From bddc1e95ef42fa735e3584d3820f8013601e7d6a Mon Sep 17 00:00:00 2001 From: Jakob Hasse Date: Thu, 1 Feb 2024 09:19:46 +0800 Subject: [PATCH] feat(rt/posix): Added FreeRTOS-Plus-POSIX message queues implementation Note: The current mq_open() implementation is changed to match the POSIX standard. --- .gitlab/CODEOWNERS | 1 + components/rt/CMakeLists.txt | 13 + components/rt/FreeRTOS_POSIX_mqueue.c | 924 ++++++++++++++++++ components/rt/FreeRTOS_POSIX_utils.c | 249 +++++ components/rt/idf_changes.md | 46 + components/rt/include/mqueue.h | 155 +++ .../rt/private_include/FreeRTOS_POSIX.h | 56 ++ .../rt/private_include/FreeRTOS_POSIX/utils.h | 149 +++ .../private_include/FreeRTOS_POSIX_internal.h | 101 ++ .../private_include/FreeRTOS_POSIX_portable.h | 59 ++ .../private_include/aws_doubly_linked_list.h | 249 +++++ .../FreeRTOS_POSIX_portable_default.h | 144 +++ components/rt/sbom.yml | 5 + .../posix_rt_test/.build-test-rules.yml | 9 + .../rt/test_apps/posix_rt_test/CMakeLists.txt | 7 + .../rt/test_apps/posix_rt_test/README.md | 2 + .../posix_rt_test/main/CMakeLists.txt | 4 + .../rt/test_apps/posix_rt_test/main/main.c | 36 + .../posix_rt_test/main/mqueue_test.cpp | 288 ++++++ .../posix_rt_test/pytest_rt_mqueue_tests.py | 13 + .../posix_rt_test/sdkconfig.defaults | 2 + docs/en/api-reference/system/pthread.rst | 65 +- examples/system/.build-test-rules.yml | 7 + examples/system/rt_mqueue/CMakeLists.txt | 8 + examples/system/rt_mqueue/README.md | 37 + examples/system/rt_mqueue/main/CMakeLists.txt | 3 + .../main/posix_mqueue_example_main.c | 101 ++ examples/system/rt_mqueue/pytest_rt_mqueue.py | 13 + tools/ci/astyle-rules.yml | 2 + tools/ci/check_copyright_config.yaml | 16 + 30 files changed, 2760 insertions(+), 4 deletions(-) create mode 100644 components/rt/CMakeLists.txt create mode 100644 components/rt/FreeRTOS_POSIX_mqueue.c create mode 100644 components/rt/FreeRTOS_POSIX_utils.c create mode 100644 components/rt/idf_changes.md create mode 100644 components/rt/include/mqueue.h create mode 100644 components/rt/private_include/FreeRTOS_POSIX.h create mode 100644 components/rt/private_include/FreeRTOS_POSIX/utils.h create mode 100644 components/rt/private_include/FreeRTOS_POSIX_internal.h create mode 100644 components/rt/private_include/FreeRTOS_POSIX_portable.h create mode 100644 components/rt/private_include/aws_doubly_linked_list.h create mode 100644 components/rt/private_include/portable/FreeRTOS_POSIX_portable_default.h create mode 100644 components/rt/sbom.yml create mode 100644 components/rt/test_apps/posix_rt_test/.build-test-rules.yml create mode 100644 components/rt/test_apps/posix_rt_test/CMakeLists.txt create mode 100644 components/rt/test_apps/posix_rt_test/README.md create mode 100644 components/rt/test_apps/posix_rt_test/main/CMakeLists.txt create mode 100644 components/rt/test_apps/posix_rt_test/main/main.c create mode 100644 components/rt/test_apps/posix_rt_test/main/mqueue_test.cpp create mode 100644 components/rt/test_apps/posix_rt_test/pytest_rt_mqueue_tests.py create mode 100644 components/rt/test_apps/posix_rt_test/sdkconfig.defaults create mode 100644 examples/system/rt_mqueue/CMakeLists.txt create mode 100644 examples/system/rt_mqueue/README.md create mode 100644 examples/system/rt_mqueue/main/CMakeLists.txt create mode 100644 examples/system/rt_mqueue/main/posix_mqueue_example_main.c create mode 100644 examples/system/rt_mqueue/pytest_rt_mqueue.py diff --git a/.gitlab/CODEOWNERS b/.gitlab/CODEOWNERS index 0d61759339..fb20a1a36b 100644 --- a/.gitlab/CODEOWNERS +++ b/.gitlab/CODEOWNERS @@ -140,6 +140,7 @@ /components/protocomm/ @esp-idf-codeowners/app-utilities/provisioning /components/pthread/ @esp-idf-codeowners/system /components/riscv/ @esp-idf-codeowners/system +/components/rt/ @esp-idf-codeowners/system /components/sdmmc/ @esp-idf-codeowners/storage /components/soc/ @esp-idf-codeowners/peripherals @esp-idf-codeowners/system /components/spi_flash/ @esp-idf-codeowners/peripherals diff --git a/components/rt/CMakeLists.txt b/components/rt/CMakeLists.txt new file mode 100644 index 0000000000..87fc2e90ed --- /dev/null +++ b/components/rt/CMakeLists.txt @@ -0,0 +1,13 @@ +idf_build_get_property(target IDF_TARGET) +if(${target} STREQUAL "linux") + idf_component_register() + if(NOT CMAKE_HOST_SYSTEM_NAME STREQUAL "Darwin") + find_library(LIBRT rt) + target_link_libraries(${COMPONENT_LIB} INTERFACE ${LIBRT}) + endif() + return() +endif() + +idf_component_register(SRCS "FreeRTOS_POSIX_mqueue.c" "FreeRTOS_POSIX_utils.c" + PRIV_INCLUDE_DIRS "private_include" + INCLUDE_DIRS "include") diff --git a/components/rt/FreeRTOS_POSIX_mqueue.c b/components/rt/FreeRTOS_POSIX_mqueue.c new file mode 100644 index 0000000000..22ebf4e0ad --- /dev/null +++ b/components/rt/FreeRTOS_POSIX_mqueue.c @@ -0,0 +1,924 @@ +/* + * Amazon FreeRTOS+POSIX V1.0.0 + * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * SPDX-FileCopyrightText: 2018 Amazon.com, Inc. or its affiliates + * + * SPDX-License-Identifier: MIT + * + * SPDX-FileContributor: 2024 Espressif Systems (Shanghai) CO LTD + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * http://aws.amazon.com/freertos + * http://www.FreeRTOS.org + */ + +/** + * @file FreeRTOS_POSIX_mqueue.c + * @brief Implementation of message queue functions in mqueue.h + */ + +/* C standard library includes. */ +#include + +#include +#include +#include +#include + +/* FreeRTOS+POSIX includes. */ +#include "FreeRTOS_POSIX.h" +#include "FreeRTOS_POSIX/utils.h" + +#include "aws_doubly_linked_list.h" +#include "esp_private/critical_section.h" + +/** + * @brief Element of the FreeRTOS queues that store mq data. + */ +typedef struct QueueElement +{ + char * pcData; /**< Data in queue. Type char* to match msg_ptr. */ + size_t xDataSize; /**< Size of data pointed by pcData. */ +} QueueElement_t; + +/** + * @brief Data structure of an mq. + * + * FreeRTOS isn't guaranteed to have a file-like abstraction, so message + * queues in this implementation are stored as a linked list (in RAM). + */ +typedef struct QueueListElement +{ + Link_t xLink; /**< Pointer to the next element in the list. */ + QueueHandle_t xQueue; /**< FreeRTOS queue handle. */ + size_t xOpenDescriptors; /**< Number of threads that have opened this queue. */ + char * pcName; /**< Null-terminated queue name. */ + struct mq_attr xAttr; /**< Queue attributes. */ + BaseType_t xPendingUnlink; /**< If pdTRUE, this queue will be unlinked once all descriptors close. */ +} QueueListElement_t; + +/*-----------------------------------------------------------*/ + +/** + * @brief Convert an absolute timespec into a tick timeout, taking into account + * queue flags. + * + * @param[in] lMessageQueueFlags Message queue flags to consider. + * @param[in] pxAbsoluteTimeout The absolute timespec to convert. + * @param[out] pxTimeoutTicks Output parameter of the timeout in ticks. + * + * @return 0 if successful; EINVAL if pxAbsoluteTimeout is invalid, or ETIMEDOUT + * if pxAbsoluteTimeout is in the past. + */ +static int prvCalculateTickTimeout( long lMessageQueueFlags, + const struct timespec * const pxAbsoluteTimeout, + TickType_t * pxTimeoutTicks ); + +/** + * @brief Add a new queue to the queue list. + * + * @param[out] ppxMessageQueue Pointer to new queue. + * @param[in] pxAttr mq_attr of the new queue. + * @param[in] pcName Name of new queue. + * @param[in] xNameLength Length of pcName. + * + * @return pdTRUE if the queue is found; pdFALSE otherwise. + */ +static BaseType_t prvCreateNewMessageQueue( QueueListElement_t ** ppxMessageQueue, + const struct mq_attr * const pxAttr, + const char * const pcName, + size_t xNameLength ); + +/** + * @brief Free all the resources used by a message queue. + * + * @param[out] pxMessageQueue Pointer to queue to free. + * + * @return nothing + */ +static void prvDeleteMessageQueue( const QueueListElement_t * const pxMessageQueue ); + +/** + * @brief Attempt to find the queue identified by pcName or xMqId in the queue list. + * + * Matches queues by pcName first; if pcName is NULL, matches by xMqId. + * @param[out] ppxQueueListElement Output parameter set when queue is found. + * @param[in] pcName A queue name to match. + * @param[in] xMessageQueueDescriptor A queue descriptor to match. + * + * @return pdTRUE if the queue is found; pdFALSE otherwise. + */ +static BaseType_t prvFindQueueInList( QueueListElement_t ** const ppxQueueListElement, + const char * const pcName, + mqd_t xMessageQueueDescriptor ); + +/** + * @brief Initialize the queue list. + * + * Performs initialization of the queue list mutex and queue list head. + * + * @return nothing + */ +static void prvInitializeQueueList( void ); + +/** + * @brief Checks that pcName is a valid name for a message queue. + * + * Also outputs the length of pcName. + * @param[in] pcName The name to check. + * @param[out] pxNameLength Output parameter for name length. + * + * @return pdTRUE if the name is valid; pdFALSE otherwise. + */ +static BaseType_t prvValidateQueueName( const char * const pcName, + size_t * pxNameLength ); + +/** + * @brief Guards access to the list of message queues. + */ +static StaticSemaphore_t xQueueListMutex = { { 0 }, .u = { 0 } }; + +/** + * @brief Head of the linked list of queues. + */ +static Link_t xQueueListHead = { 0 }; + +DEFINE_CRIT_SECTION_LOCK_STATIC( critical_section_lock ); + +/*-----------------------------------------------------------*/ + +static int prvCalculateTickTimeout( long lMessageQueueFlags, + const struct timespec * const pxAbsoluteTimeout, + TickType_t * pxTimeoutTicks ) +{ + int iStatus = 0; + + /* Check for nonblocking queue. */ + if( lMessageQueueFlags & O_NONBLOCK ) + { + /* No additional checks are done for nonblocking queues. Timeout is 0. */ + *pxTimeoutTicks = 0; + } + else + { + /* No absolute timeout given. Block forever. */ + if( pxAbsoluteTimeout == NULL ) + { + *pxTimeoutTicks = portMAX_DELAY; + } + else + { + /* Check that the given timespec is valid. */ + if( UTILS_ValidateTimespec( pxAbsoluteTimeout ) == false ) + { + iStatus = EINVAL; + } + + /* Convert absolute timespec to ticks. */ + if( ( iStatus == 0 ) && + ( UTILS_AbsoluteTimespecToTicks( pxAbsoluteTimeout, pxTimeoutTicks ) != 0 ) ) + { + iStatus = ETIMEDOUT; + } + } + } + + return iStatus; +} + +/*-----------------------------------------------------------*/ + +static BaseType_t prvCreateNewMessageQueue( QueueListElement_t ** ppxMessageQueue, + const struct mq_attr * const pxAttr, + const char * const pcName, + size_t xNameLength ) +{ + BaseType_t xStatus = pdTRUE; + + /* Allocate space for a new queue element. */ + *ppxMessageQueue = pvPortMalloc( sizeof( QueueListElement_t ) ); + + /* Check that memory allocation succeeded. */ + if( *ppxMessageQueue == NULL ) + { + xStatus = pdFALSE; + } + + /* Create the FreeRTOS queue. */ + if( xStatus == pdTRUE ) + { + ( *ppxMessageQueue )->xQueue = + xQueueCreate( pxAttr->mq_maxmsg, sizeof( QueueElement_t ) ); + + /* Check that queue creation succeeded. */ + if( ( *ppxMessageQueue )->xQueue == NULL ) + { + vPortFree( *ppxMessageQueue ); + xStatus = pdFALSE; + } + } + + if( xStatus == pdTRUE ) + { + /* Allocate space for the queue name plus null-terminator. */ + ( *ppxMessageQueue )->pcName = pvPortMalloc( xNameLength + 1 ); + + /* Check that memory was successfully allocated for queue name. */ + if( ( *ppxMessageQueue )->pcName == NULL ) + { + vQueueDelete( ( *ppxMessageQueue )->xQueue ); + vPortFree( *ppxMessageQueue ); + xStatus = pdFALSE; + } + else + { + /* Copy queue name. Copying xNameLength+1 will cause strncpy to add + * the null-terminator. */ + ( void ) strncpy( ( *ppxMessageQueue )->pcName, pcName, xNameLength + 1 ); + } + } + + if( xStatus == pdTRUE ) + { + /* Copy attributes. */ + ( *ppxMessageQueue )->xAttr = *pxAttr; + + /* A newly-created queue will have 1 open descriptor for it. */ + ( *ppxMessageQueue )->xOpenDescriptors = 1; + + /* A newly-created queue will not be pending unlink. */ + ( *ppxMessageQueue )->xPendingUnlink = pdFALSE; + + /* Add the new queue to the list. */ + listADD( &xQueueListHead, &( *ppxMessageQueue )->xLink ); + } + + return xStatus; +} + +/*-----------------------------------------------------------*/ + +static void prvDeleteMessageQueue( const QueueListElement_t * const pxMessageQueue ) +{ + QueueElement_t xQueueElement = { 0 }; + + /* Free all data in the queue. It's assumed that no more data will be added + * to the queue, so xQueueReceive does not block. */ + while( xQueueReceive( pxMessageQueue->xQueue, + ( void * ) &xQueueElement, + 0 ) == pdTRUE ) + { + vPortFree( xQueueElement.pcData ); + } + + /* Free memory used by this message queue. */ + vQueueDelete( pxMessageQueue->xQueue ); + vPortFree( ( void * ) pxMessageQueue->pcName ); + vPortFree( ( void * ) pxMessageQueue ); +} + +/*-----------------------------------------------------------*/ + +static BaseType_t prvFindQueueInList( QueueListElement_t ** const ppxQueueListElement, + const char * const pcName, + mqd_t xMessageQueueDescriptor ) +{ + Link_t * pxQueueListLink = NULL; + QueueListElement_t * pxMessageQueue = NULL; + BaseType_t xQueueFound = pdFALSE; + + /* Iterate through the list of queues. */ + listFOR_EACH( pxQueueListLink, &xQueueListHead ) + { + pxMessageQueue = listCONTAINER( pxQueueListLink, QueueListElement_t, xLink ); + + /* Match by name first if provided. */ + if( ( pcName != NULL ) && ( strcmp( pxMessageQueue->pcName, pcName ) == 0 ) ) + { + xQueueFound = pdTRUE; + break; + } + /* If name doesn't match, match by descriptor. */ + else + { + if( ( mqd_t ) pxMessageQueue == xMessageQueueDescriptor ) + { + xQueueFound = pdTRUE; + break; + } + } + } + + /* If the queue was found, set the output parameter. */ + if( ( xQueueFound == pdTRUE ) && ( ppxQueueListElement != NULL ) ) + { + *ppxQueueListElement = pxMessageQueue; + } + + return xQueueFound; +} + +/*-----------------------------------------------------------*/ + +static void prvInitializeQueueList( void ) +{ + /* Keep track of whether the queue list has been initialized. */ + static BaseType_t xQueueListInitialized = pdFALSE; + + /* Check if queue list needs to be initialized. */ + if( xQueueListInitialized == pdFALSE ) + { + /* Initialization must be in a critical section to prevent two threads + * from initializing at the same time. */ + esp_os_enter_critical( &critical_section_lock ); + + /* Check again that queue list is still uninitialized, i.e. it wasn't + * initialized while this function was waiting to enter the critical + * section. */ + if( xQueueListInitialized == pdFALSE ) + { + /* Initialize the queue list mutex and list head. */ + ( void ) xSemaphoreCreateMutexStatic( &xQueueListMutex ); + listINIT_HEAD( &xQueueListHead ); + xQueueListInitialized = pdTRUE; + } + + /* Exit the critical section. */ + esp_os_exit_critical( &critical_section_lock ); + } +} + +/*-----------------------------------------------------------*/ + +static BaseType_t prvValidateQueueName( const char * const pcName, + size_t * pxNameLength ) +{ + BaseType_t xStatus = pdTRUE; + size_t xNameLength = 0; + + /* All message queue names must start with '/'. */ + if( pcName[ 0 ] != '/' ) + { + xStatus = pdFALSE; + } + else + { + /* Get the length of pcName, excluding the first '/' and null-terminator. */ + xNameLength = UTILS_strnlen( pcName, NAME_MAX + 2 ); + + if( xNameLength == NAME_MAX + 2 ) + { + /* Name too long. */ + xStatus = pdFALSE; + } + else + { + /* Name length passes, set output parameter. */ + *pxNameLength = xNameLength; + } + } + + return xStatus; +} + +/*-----------------------------------------------------------*/ + +int mq_close( mqd_t mqdes ) +{ + int iStatus = 0; + QueueListElement_t * pxMessageQueue = ( QueueListElement_t * ) mqdes; + BaseType_t xQueueRemoved = pdFALSE; + + /* Initialize the queue list, if needed. */ + prvInitializeQueueList(); + + /* Lock the mutex that guards access to the queue list. This call will + * never fail because it blocks forever. */ + ( void ) xSemaphoreTake( ( SemaphoreHandle_t ) &xQueueListMutex, portMAX_DELAY ); + + /* Attempt to find the message queue based on the given descriptor. */ + if( prvFindQueueInList( NULL, NULL, mqdes ) == pdTRUE ) + { + /* Decrement the number of open descriptors. */ + if( pxMessageQueue->xOpenDescriptors > 0 ) + { + pxMessageQueue->xOpenDescriptors--; + } + + /* Check if the queue has any more open descriptors. */ + if( pxMessageQueue->xOpenDescriptors == 0 ) + { + /* If no open descriptors remain and mq_unlink has already been called, + * remove the queue. */ + if( pxMessageQueue->xPendingUnlink == pdTRUE ) + { + listREMOVE( &pxMessageQueue->xLink ); + + /* Set the flag to delete the queue. Deleting the queue is deferred + * until xQueueListMutex is released. */ + xQueueRemoved = pdTRUE; + } + /* Otherwise, wait for the call to mq_unlink. */ + else + { + pxMessageQueue->xPendingUnlink = pdTRUE; + } + } + } + else + { + /* Queue not found; bad descriptor. */ + errno = EBADF; + iStatus = -1; + } + + /* Release the mutex protecting the queue list. */ + ( void ) xSemaphoreGive( ( SemaphoreHandle_t ) &xQueueListMutex ); + + /* Delete all resources used by the queue if needed. */ + if( xQueueRemoved == pdTRUE ) + { + prvDeleteMessageQueue( pxMessageQueue ); + } + + return iStatus; +} + +/*-----------------------------------------------------------*/ + +int mq_getattr( mqd_t mqdes, + struct mq_attr * mqstat ) +{ + int iStatus = 0; + QueueListElement_t * pxMessageQueue = ( QueueListElement_t * ) mqdes; + + /* Lock the mutex that guards access to the queue list. This call will + * never fail because it blocks forever. */ + ( void ) xSemaphoreTake( ( SemaphoreHandle_t ) &xQueueListMutex, portMAX_DELAY ); + + /* Find the mq referenced by mqdes. */ + if( prvFindQueueInList( NULL, NULL, mqdes ) == pdTRUE ) + { + /* Update the number of messages in the queue and copy the attributes + * into mqstat. */ + pxMessageQueue->xAttr.mq_curmsgs = ( long ) uxQueueMessagesWaiting( pxMessageQueue->xQueue ); + *mqstat = pxMessageQueue->xAttr; + } + else + { + /* Queue not found; bad descriptor. */ + errno = EBADF; + iStatus = -1; + } + + /* Release the mutex protecting the queue list. */ + ( void ) xSemaphoreGive( ( SemaphoreHandle_t ) &xQueueListMutex ); + + return iStatus; +} + +/*-----------------------------------------------------------*/ + +/* Changed function signature and argument processing to match the POSIX standard */ +mqd_t mq_open( const char * name, + int oflag, + ... ) +{ + mode_t mode = 0; + struct mq_attr * attr = NULL; + + if( oflag & O_CREAT ) + { + va_list args; + va_start( args, oflag ); + mode = va_arg( args, mode_t ); + attr = va_arg( args, struct mq_attr * ); + va_end( args ); + } + + mqd_t xMessageQueue = NULL; + size_t xNameLength = 0; + + /* Default mq_attr. */ + struct mq_attr xQueueCreationAttr = + { + .mq_flags = 0, + .mq_maxmsg = posixconfigMQ_MAX_MESSAGES, + .mq_msgsize = posixconfigMQ_MAX_SIZE, + .mq_curmsgs = 0 + }; + + /* Silence warnings about unused parameters. */ + ( void ) mode; + + /* Initialize the queue list, if needed. */ + prvInitializeQueueList(); + + /* Check queue name. */ + if( prvValidateQueueName( name, &xNameLength ) == pdFALSE ) + { + /* Invalid name. */ + errno = EINVAL; + xMessageQueue = ( mqd_t ) -1; + } + + /* Check attributes, if O_CREATE is specified and attr is given. */ + if( xMessageQueue == NULL ) + { + if( ( oflag & O_CREAT ) && ( attr != NULL ) && ( ( attr->mq_maxmsg <= 0 ) || ( attr->mq_msgsize <= 0 ) ) ) + { + /* Invalid mq_attr.mq_maxmsg or mq_attr.mq_msgsize. */ + errno = EINVAL; + xMessageQueue = ( mqd_t ) -1; + } + } + + if( xMessageQueue == NULL ) + { + /* Lock the mutex that guards access to the queue list. This call will + * never fail because it blocks forever. */ + ( void ) xSemaphoreTake( ( SemaphoreHandle_t ) &xQueueListMutex, portMAX_DELAY ); + + /* Search the queue list to check if the queue exists. */ + if( prvFindQueueInList( ( QueueListElement_t ** ) &xMessageQueue, + name, + ( mqd_t ) NULL ) == pdTRUE ) + { + /* If the mq exists, check that this function wasn't called with + * O_CREAT and O_EXCL. */ + if( ( oflag & O_EXCL ) && ( oflag & O_CREAT ) ) + { + errno = EEXIST; + xMessageQueue = ( mqd_t ) -1; + } + else + { + /* Check if the mq has been unlinked and is pending removal. */ + if( ( ( QueueListElement_t * ) xMessageQueue )->xPendingUnlink == pdTRUE ) + { + /* Queue pending deletion. Don't allow it to be re-opened. */ + errno = EINVAL; + xMessageQueue = ( mqd_t ) -1; + } + else + { + /* Increase count of open file descriptors for queue. */ + ( ( QueueListElement_t * ) xMessageQueue )->xOpenDescriptors++; + } + } + } + /* Queue does not exist. */ + else + { + /* Only create the new queue if O_CREAT was specified. */ + if( oflag & O_CREAT ) + { + /* Copy attributes if provided. */ + if( attr != NULL ) + { + xQueueCreationAttr = *attr; + } + + /* Copy oflags. */ + xQueueCreationAttr.mq_flags = ( long ) oflag; + + /* Create the new message queue. */ + if( prvCreateNewMessageQueue( ( QueueListElement_t ** ) &xMessageQueue, + &xQueueCreationAttr, + name, + xNameLength ) == pdFALSE ) + { + errno = ENOSPC; + xMessageQueue = ( mqd_t ) -1; + } + } + else + { + errno = ENOENT; + xMessageQueue = ( mqd_t ) -1; + } + } + + /* Release the mutex protecting the queue list. */ + ( void ) xSemaphoreGive( ( SemaphoreHandle_t ) &xQueueListMutex ); + } + + return xMessageQueue; +} + +/*-----------------------------------------------------------*/ + +ssize_t mq_receive( mqd_t mqdes, + char * msg_ptr, + size_t msg_len, + unsigned int * msg_prio ) +{ + return mq_timedreceive( mqdes, msg_ptr, msg_len, msg_prio, NULL ); +} + +/*-----------------------------------------------------------*/ + +int mq_send( mqd_t mqdes, + const char * msg_ptr, + size_t msg_len, + unsigned msg_prio ) +{ + return mq_timedsend( mqdes, msg_ptr, msg_len, msg_prio, NULL ); +} + +/*-----------------------------------------------------------*/ + +ssize_t mq_timedreceive( mqd_t mqdes, + char * msg_ptr, + size_t msg_len, + unsigned * msg_prio, + const struct timespec * abstime ) +{ + ssize_t xStatus = 0; + int iCalculateTimeoutReturn = 0; + TickType_t xTimeoutTicks = 0; + QueueListElement_t * pxMessageQueue = ( QueueListElement_t * ) mqdes; + QueueElement_t xReceiveData = { 0 }; + + /* Silence warnings about unused parameters. */ + ( void ) msg_prio; + + /* Lock the mutex that guards access to the queue list. This call will + * never fail because it blocks forever. */ + ( void ) xSemaphoreTake( ( SemaphoreHandle_t ) &xQueueListMutex, portMAX_DELAY ); + + /* Find the mq referenced by mqdes. */ + if( prvFindQueueInList( NULL, NULL, mqdes ) == pdFALSE ) + { + /* Queue not found; bad descriptor. */ + errno = EBADF; + xStatus = -1; + } + + /* Verify that msg_len is large enough. */ + if( xStatus == 0 ) + { + if( msg_len < ( size_t ) pxMessageQueue->xAttr.mq_msgsize ) + { + /* msg_len too small. */ + errno = EMSGSIZE; + xStatus = -1; + } + } + + if( xStatus == 0 ) + { + /* Convert abstime to a tick timeout. */ + iCalculateTimeoutReturn = prvCalculateTickTimeout( pxMessageQueue->xAttr.mq_flags, + abstime, + &xTimeoutTicks ); + + if( iCalculateTimeoutReturn != 0 ) + { + errno = iCalculateTimeoutReturn; + xStatus = -1; + } + } + + /* Release the mutex protecting the queue list. */ + ( void ) xSemaphoreGive( ( SemaphoreHandle_t ) &xQueueListMutex ); + + if( xStatus == 0 ) + { + /* Receive data from the FreeRTOS queue. */ + if( xQueueReceive( pxMessageQueue->xQueue, + &xReceiveData, + xTimeoutTicks ) == pdFALSE ) + { + /* If queue receive fails, set the appropriate errno. */ + if( pxMessageQueue->xAttr.mq_flags & O_NONBLOCK ) + { + /* Set errno to EAGAIN for nonblocking mq. */ + errno = EAGAIN; + } + else + { + /* Otherwise, set errno to ETIMEDOUT. */ + errno = ETIMEDOUT; + } + + xStatus = -1; + } + } + + if( xStatus == 0 ) + { + /* Get the length of data for return value. */ + xStatus = ( ssize_t ) xReceiveData.xDataSize; + + /* Copy received data into given buffer, then free it. */ + ( void ) memcpy( msg_ptr, xReceiveData.pcData, xReceiveData.xDataSize ); + vPortFree( xReceiveData.pcData ); + } + + return xStatus; +} + +/*-----------------------------------------------------------*/ + +int mq_timedsend( mqd_t mqdes, + const char * msg_ptr, + size_t msg_len, + unsigned int msg_prio, + const struct timespec * abstime ) +{ + int iStatus = 0, iCalculateTimeoutReturn = 0; + TickType_t xTimeoutTicks = 0; + QueueListElement_t * pxMessageQueue = ( QueueListElement_t * ) mqdes; + QueueElement_t xSendData = { 0 }; + + /* Silence warnings about unused parameters. */ + ( void ) msg_prio; + + /* Lock the mutex that guards access to the queue list. This call will + * never fail because it blocks forever. */ + ( void ) xSemaphoreTake( ( SemaphoreHandle_t ) &xQueueListMutex, portMAX_DELAY ); + + /* Find the mq referenced by mqdes. */ + if( prvFindQueueInList( NULL, NULL, mqdes ) == pdFALSE ) + { + /* Queue not found; bad descriptor. */ + errno = EBADF; + iStatus = -1; + } + + /* Verify that mq_msgsize is large enough. */ + if( iStatus == 0 ) + { + if( msg_len > ( size_t ) pxMessageQueue->xAttr.mq_msgsize ) + { + /* msg_len too large. */ + errno = EMSGSIZE; + iStatus = -1; + } + } + + if( iStatus == 0 ) + { + /* Convert abstime to a tick timeout. */ + iCalculateTimeoutReturn = prvCalculateTickTimeout( pxMessageQueue->xAttr.mq_flags, + abstime, + &xTimeoutTicks ); + + if( iCalculateTimeoutReturn != 0 ) + { + errno = iCalculateTimeoutReturn; + iStatus = -1; + } + } + + /* Release the mutex protecting the queue list. */ + ( void ) xSemaphoreGive( ( SemaphoreHandle_t ) &xQueueListMutex ); + + /* Allocate memory for the message. */ + if( iStatus == 0 ) + { + xSendData.xDataSize = msg_len; + xSendData.pcData = pvPortMalloc( msg_len ); + + /* Check that memory allocation succeeded. */ + if( xSendData.pcData == NULL ) + { + /* msg_len too large. */ + errno = EMSGSIZE; + iStatus = -1; + } + else + { + /* Copy the data to send. */ + ( void ) memcpy( xSendData.pcData, msg_ptr, msg_len ); + } + } + + if( iStatus == 0 ) + { + /* Send data to the FreeRTOS queue. */ + if( xQueueSend( pxMessageQueue->xQueue, + &xSendData, + xTimeoutTicks ) == pdFALSE ) + { + /* If queue send fails, set the appropriate errno. */ + if( pxMessageQueue->xAttr.mq_flags & O_NONBLOCK ) + { + /* Set errno to EAGAIN for nonblocking mq. */ + errno = EAGAIN; + } + else + { + /* Otherwise, set errno to ETIMEDOUT. */ + errno = ETIMEDOUT; + } + + /* Free the allocated queue data. */ + vPortFree( xSendData.pcData ); + + iStatus = -1; + } + } + + return iStatus; +} + +/*-----------------------------------------------------------*/ + +int mq_unlink( const char * name ) +{ + int iStatus = 0; + size_t xNameSize = 0; + BaseType_t xQueueRemoved = pdFALSE; + QueueListElement_t * pxMessageQueue = NULL; + + /* Initialize the queue list, if needed. */ + prvInitializeQueueList(); + + /* Check queue name. */ + if( prvValidateQueueName( name, &xNameSize ) == pdFALSE ) + { + /* Error with mq name. */ + errno = EINVAL; + iStatus = -1; + } + + if( iStatus == 0 ) + { + /* Lock the mutex that guards access to the queue list. This call will + * never fail because it blocks forever. */ + ( void ) xSemaphoreTake( ( SemaphoreHandle_t ) &xQueueListMutex, portMAX_DELAY ); + + /* Check if the named queue exists. */ + if( prvFindQueueInList( &pxMessageQueue, name, ( mqd_t ) NULL ) == pdTRUE ) + { + /* If the queue exists and there are no open descriptors to it, + * remove it from the list. */ + if( pxMessageQueue->xOpenDescriptors == 0 ) + { + listREMOVE( &pxMessageQueue->xLink ); + + /* Set the flag to delete the queue. Deleting the queue is deferred + * until xQueueListMutex is released. */ + xQueueRemoved = pdTRUE; + } + else + { + /* If the queue has open descriptors, set the pending unlink flag + * so that mq_close will free its resources. */ + pxMessageQueue->xPendingUnlink = pdTRUE; + } + } + else + { + /* The named message queue doesn't exist. */ + errno = ENOENT; + iStatus = -1; + } + + /* Release the mutex protecting the queue list. */ + ( void ) xSemaphoreGive( ( SemaphoreHandle_t ) &xQueueListMutex ); + } + + /* Delete all resources used by the queue if needed. */ + if( xQueueRemoved == pdTRUE ) + { + prvDeleteMessageQueue( pxMessageQueue ); + } + + return iStatus; +} + +/* specified but not implemented functions, return ENOSYS */ +int mq_notify( mqd_t, + const struct sigevent * ) +{ + return ENOSYS; +} + +int mq_setattr( mqd_t, + const struct mq_attr * restrict, + struct mq_attr * restrict ) +{ + return ENOSYS; +} + +/*-----------------------------------------------------------*/ diff --git a/components/rt/FreeRTOS_POSIX_utils.c b/components/rt/FreeRTOS_POSIX_utils.c new file mode 100644 index 0000000000..d9d30fa409 --- /dev/null +++ b/components/rt/FreeRTOS_POSIX_utils.c @@ -0,0 +1,249 @@ +/* + * Amazon FreeRTOS+POSIX V1.0.0 + * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * SPDX-FileCopyrightText: 2018 Amazon.com, Inc. or its affiliates + * + * SPDX-License-Identifier: MIT + * + * SPDX-FileContributor: 2024 Espressif Systems (Shanghai) CO LTD + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * http://aws.amazon.com/freertos + * http://www.FreeRTOS.org + */ + +/** + * @file FreeRTOS_POSIX_utils.c + * @brief Implementation of utility functions in utils.h + */ + +/* C standard library includes. */ +#include + +#include /* Added by Espressif */ +#include +#include + +/* FreeRTOS+POSIX includes. */ +#include "FreeRTOS_POSIX.h" +#include "FreeRTOS_POSIX/utils.h" + +/*-----------------------------------------------------------*/ + +/* This function has been modified from the original by Espressif */ +size_t UTILS_strnlen( const char * const pcString, + size_t xMaxLength ) +{ + return strnlen( pcString, xMaxLength ); +} + +/*-----------------------------------------------------------*/ + +int UTILS_AbsoluteTimespecToTicks( const struct timespec * const pxAbsoluteTime, + TickType_t * const pxResult ) +{ + int iStatus = 0; + struct timespec xCurrentTime = { 0 }, xDifference = { 0 }; + + /* Check parameters. */ + if( ( pxAbsoluteTime == NULL ) || ( pxResult == NULL ) ) + { + iStatus = EINVAL; + } + + /* Get the current time. */ + if( iStatus == 0 ) + { + if( clock_gettime( CLOCK_REALTIME, &xCurrentTime ) != 0 ) + { + iStatus = errno; + } + } + + /* Calculate the difference between the current time and pxAbsoluteTime. */ + if( iStatus == 0 ) + { + if( UTILS_TimespecSubtract( &xDifference, pxAbsoluteTime, &xCurrentTime ) != 0 ) + { + /* pxAbsoluteTime was in the past. */ + iStatus = ETIMEDOUT; + } + } + + /* Convert the time difference to ticks. */ + if( iStatus == 0 ) + { + iStatus = UTILS_TimespecToTicks( &xDifference, pxResult ); + } + + return iStatus; +} + +/*-----------------------------------------------------------*/ + +int UTILS_TimespecToTicks( const struct timespec * const pxTimespec, + TickType_t * const pxResult ) +{ + int iStatus = 0; + uint64_t ullTotalTicks = 0; + long lNanoseconds = 0; + + /* Check parameters. */ + if( ( pxTimespec == NULL ) || ( pxResult == NULL ) ) + { + iStatus = EINVAL; + } + else if( ( pxTimespec != NULL ) && ( UTILS_ValidateTimespec( pxTimespec ) == false ) ) + { + iStatus = EINVAL; + } + + if( iStatus == 0 ) + { + /* Convert timespec.tv_sec to ticks. */ + ullTotalTicks = ( uint64_t ) configTICK_RATE_HZ * ( uint64_t ) ( pxTimespec->tv_sec ); + + /* Convert timespec.tv_nsec to ticks. This value does not have to be checked + * for overflow because a valid timespec has 0 <= tv_nsec < 1000000000 and + * NANOSECONDS_PER_TICK > 1. */ + lNanoseconds = pxTimespec->tv_nsec / ( long ) NANOSECONDS_PER_TICK + /* Whole nanoseconds. */ + ( long ) ( pxTimespec->tv_nsec % ( long ) NANOSECONDS_PER_TICK != 0 ); /* Add 1 to round up if needed. */ + + /* Add the nanoseconds to the total ticks. */ + ullTotalTicks += ( uint64_t ) lNanoseconds; + + /* Write result. */ + *pxResult = ( TickType_t ) ullTotalTicks; + } + + return iStatus; +} + +/*-----------------------------------------------------------*/ + +void UTILS_NanosecondsToTimespec( int64_t llSource, + struct timespec * const pxDestination ) +{ + long lCarrySec = 0; + + /* Convert to timespec. */ + pxDestination->tv_sec = ( time_t ) ( llSource / NANOSECONDS_PER_SECOND ); + pxDestination->tv_nsec = ( long ) ( llSource % NANOSECONDS_PER_SECOND ); + + /* Subtract from tv_sec if tv_nsec < 0. */ + if( pxDestination->tv_nsec < 0L ) + { + /* Compute the number of seconds to carry. */ + lCarrySec = ( pxDestination->tv_nsec / ( long ) NANOSECONDS_PER_SECOND ) + 1L; + + pxDestination->tv_sec -= ( time_t ) ( lCarrySec ); + pxDestination->tv_nsec += lCarrySec * ( long ) NANOSECONDS_PER_SECOND; + } +} + +/*-----------------------------------------------------------*/ + +int UTILS_TimespecAdd( struct timespec * const pxResult, + const struct timespec * const x, + const struct timespec * const y ) +{ + int64_t llResult64 = 0; + + /* Check parameters. */ + if( ( pxResult == NULL ) || ( x == NULL ) || ( y == NULL ) ) + { + return -1; + } + + /* Perform addition. */ + llResult64 = ( ( ( int64_t ) ( x->tv_sec ) * NANOSECONDS_PER_SECOND ) + ( int64_t ) ( x->tv_nsec ) ) + + ( ( ( int64_t ) ( y->tv_sec ) * NANOSECONDS_PER_SECOND ) + ( int64_t ) ( y->tv_nsec ) ); + + /* Convert result to timespec. */ + UTILS_NanosecondsToTimespec( llResult64, pxResult ); + + return ( int ) ( llResult64 < 0LL ); +} + +/*-----------------------------------------------------------*/ + +int UTILS_TimespecAddNanoseconds( struct timespec * const pxResult, + const struct timespec * const x, + int64_t llNanoseconds ) +{ + struct timespec y = { .tv_sec = ( time_t ) 0, .tv_nsec = 0L }; + + /* Check parameters. */ + if( ( pxResult == NULL ) || ( x == NULL ) ) + { + return -1; + } + + /* Convert llNanoseconds to a timespec. */ + UTILS_NanosecondsToTimespec( llNanoseconds, &y ); + + /* Perform addition. */ + return UTILS_TimespecAdd( pxResult, x, &y ); +} + +/*-----------------------------------------------------------*/ + +int UTILS_TimespecSubtract( struct timespec * const pxResult, + const struct timespec * const x, + const struct timespec * const y ) +{ + int64_t llResult64 = 0; + + /* Check parameters. */ + if( ( pxResult == NULL ) || ( x == NULL ) || ( y == NULL ) ) + { + return -1; + } + + /* Perform addition. */ + llResult64 = ( ( ( int64_t ) ( x->tv_sec ) * NANOSECONDS_PER_SECOND ) + ( int64_t ) ( x->tv_nsec ) ) + - ( ( ( int64_t ) ( y->tv_sec ) * NANOSECONDS_PER_SECOND ) + ( int64_t ) ( y->tv_nsec ) ); + + /* Convert result to timespec. */ + UTILS_NanosecondsToTimespec( llResult64, pxResult ); + + return ( int ) ( llResult64 < 0LL ); +} + +/*-----------------------------------------------------------*/ + +bool UTILS_ValidateTimespec( const struct timespec * const pxTimespec ) +{ + bool xReturn = false; + + if( pxTimespec != NULL ) + { + /* Verify 0 <= tv_nsec < 1000000000. */ + if( ( pxTimespec->tv_nsec >= 0 ) && + ( pxTimespec->tv_nsec < NANOSECONDS_PER_SECOND ) ) + { + xReturn = true; + } + } + + return xReturn; +} + +/*-----------------------------------------------------------*/ diff --git a/components/rt/idf_changes.md b/components/rt/idf_changes.md new file mode 100644 index 0000000000..06bdf14f5d --- /dev/null +++ b/components/rt/idf_changes.md @@ -0,0 +1,46 @@ +# ESP-IDF Changes + +This document is used to track all changes made to the FreeRTOS-Plus-POSIX V1.0.0 source code used in ESP-IDF. + +## License Headers + +- Added `SPDX-FileCopyrightText` and `SPDX-FileContributor` tags to all files to pass ESP-IDF pre-commit checks. + +## POSIX errno + +Instead of relying on the FreeRTOS `errno` scheme by enabling `configUSE_POSIX_ERRNO`, the `errno` provided by newlib is used. `configUSE_POSIX_ERRNO` should stay disabled. + +## API Changes + +`mq_open()` has been changed to make the arguments `mode` and `attr` optional, as specified by [POSIX](https://pubs.opengroup.org/onlinepubs/9699919799/functions/mq_open.html). + +The following functions are stubs and always return `ENOSYS`: +* `mq_notify()` +* `mq_setattr()` + +## Header File Organization + +In the original FreeRTOS-Plus-POSIX project, the POSIX header files are provided in the sub directory `FreeRTOS-Plus-POSIX`. In ESP-IDF, however, the POSIX header files do not need any prefix. This increases compatibility with applications originally written for other platforms. All files originating from the FreeRTOS-Plus-POSIX project have been modified to reflect the different include path. + +Wherever possible, ESP-IDF is using header files from newlib instead of the header files from FreeRTOS-Plus-POSIX. + +In some cases, additional includes have been added to files from FreeRTOS-Plus-POSIX to simplify building. + +## Critical Sections + +The critical sections have been changed according to the ESP-IDF critical section API. + +## Definitions + +The following definitions have been added to the private include `utils.h`: +``` +MICROSECONDS_PER_SECOND +NANOSECONDS_PER_SECOND +NANOSECONDS_PER_TICK +``` + +In FreeRTOS-Plus-POSIX, they are located in the custom `time.h`, but ESP-IDF uses newlib's `time.h`, where these definitions are not present. + +## Code Format + +- Files have been formatted by [`uncrustify`](https://github.com/uncrustify/uncrustify), using [FreeRTOS' `uncrustify.cfc`](../freertos/FreeRTOS-Kernel/uncrustify.cfg). diff --git a/components/rt/include/mqueue.h b/components/rt/include/mqueue.h new file mode 100644 index 0000000000..c0927cc6a9 --- /dev/null +++ b/components/rt/include/mqueue.h @@ -0,0 +1,155 @@ +/* + * Amazon FreeRTOS+POSIX V1.0.0 + * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * SPDX-FileCopyrightText: 2018 Amazon.com, Inc. or its affiliates + * + * SPDX-License-Identifier: MIT + * + * SPDX-FileContributor: 2024 Espressif Systems (Shanghai) CO LTD + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * http://aws.amazon.com/freertos + * http://www.FreeRTOS.org + */ + +/** + * @file mqueue.h + * @brief Message queues. + * + * http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/mqueue.h.html + */ + +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Message queue descriptor. + */ +typedef void * mqd_t; + +/** + * @brief Message queue attributes. + */ +struct mq_attr +{ + long mq_flags; /**< Message queue flags. */ + long mq_maxmsg; /**< Maximum number of messages. */ + long mq_msgsize; /**< Maximum message size. */ + long mq_curmsgs; /**< Number of messages currently queued. */ +}; + +/** + * @brief Close a message queue. + * + * Please refer to http://pubs.opengroup.org/onlinepubs/9699919799/functions/mq_close.html for more details. + */ +int mq_close( mqd_t mqdes ); + +/** + * @brief Get message queue attributes. + * + * Please refer to http://pubs.opengroup.org/onlinepubs/9699919799/functions/mq_getattr.html for more details. + */ +int mq_getattr( mqd_t mqdes, + struct mq_attr * mqstat ); + +/** + * @brief Open a message queue. + * + * Please refer to http://pubs.opengroup.org/onlinepubs/9699919799/functions/mq_open.html for more details. + * + * @note Supported name pattern: leading <slash> character in name is always required; + * the maximum length (excluding null-terminator) of the name argument can be NAME_MAX + 2. + * @note mode argument is not supported. + * @note Supported oflags: O_RDWR, O_CREAT, O_EXCL, and O_NONBLOCK. + */ +mqd_t mq_open( const char * name, + int oflag, + ... ); + +/** + * @brief Receive a message from a message queue. + * + * Please refer to http://pubs.opengroup.org/onlinepubs/9699919799/functions/mq_receive.html for more details. + * + * @note msg_prio argument is not supported. Messages are not checked for corruption. + */ +ssize_t mq_receive( mqd_t mqdes, + char * msg_ptr, + size_t msg_len, + unsigned int * msg_prio ); + +/** + * @brief Send a message to a message queue. + * + * Please refer to http://pubs.opengroup.org/onlinepubs/9699919799/functions/mq_send.html for more details. + * + * @note msg_prio argument is not supported. + */ +int mq_send( mqd_t mqdes, + const char * msg_ptr, + size_t msg_len, + unsigned msg_prio ); + +/** + * @brief Receive a message from a message queue with timeout. + * + * Please refer to http://pubs.opengroup.org/onlinepubs/9699919799/functions/mq_timedreceive.html for more details. + * + * @note msg_prio argument is not supported. Messages are not checked for corruption. + */ +ssize_t mq_timedreceive( mqd_t mqdes, + char * msg_ptr, + size_t msg_len, + unsigned * msg_prio, + const struct timespec * abstime ); + +/** + * @brief Send a message to a message queue with timeout. + * + * Please refer to http://pubs.opengroup.org/onlinepubs/9699919799/functions/mq_timedsend.html for more details. + * + * @note msg_prio argument is not supported. + */ +int mq_timedsend( mqd_t mqdes, + const char * msg_ptr, + size_t msg_len, + unsigned msg_prio, + const struct timespec * abstime ); + +/** + * @brief Remove a message queue. + * + * Please refer to http://pubs.opengroup.org/onlinepubs/9699919799/functions/mq_unlink.html for more details. + */ +int mq_unlink( const char * name ); + +/* Added by Espressif - specified but not implemented functions, return ENOSYS */ +int mq_notify( mqd_t, const struct sigevent * ); +int mq_setattr( mqd_t, const struct mq_attr *, struct mq_attr * ); + +#ifdef __cplusplus +} +#endif diff --git a/components/rt/private_include/FreeRTOS_POSIX.h b/components/rt/private_include/FreeRTOS_POSIX.h new file mode 100644 index 0000000000..87d2e9b1e5 --- /dev/null +++ b/components/rt/private_include/FreeRTOS_POSIX.h @@ -0,0 +1,56 @@ +/* + * Amazon FreeRTOS+POSIX V1.0.0 + * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * SPDX-FileCopyrightText: 2018 Amazon.com, Inc. or its affiliates + * + * SPDX-License-Identifier: MIT + * + * SPDX-FileContributor: 2024 Espressif Systems (Shanghai) CO LTD + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * http://aws.amazon.com/freertos + * http://www.FreeRTOS.org + */ + +/** + * @file FreeRTOS_POSIX.h + * @brief FreeRTOS+POSIX header. + * + * This file must be included before all other FreeRTOS+POSIX includes. + */ + +#ifndef _FREERTOS_POSIX_H_ +#define _FREERTOS_POSIX_H_ + +/* FreeRTOS+POSIX platform-specific configuration headers. */ +#include "FreeRTOS_POSIX_portable.h" +#include "portable/FreeRTOS_POSIX_portable_default.h" + +/* FreeRTOS includes. */ +#include "freertos/FreeRTOS.h" +#include "freertos/event_groups.h" +#include "freertos/semphr.h" +#include "freertos/task.h" + +/* FreeRTOS+POSIX data types and internal structs. */ +#include +#include "FreeRTOS_POSIX_internal.h" + +#endif /* _FREERTOS_POSIX_H_ */ diff --git a/components/rt/private_include/FreeRTOS_POSIX/utils.h b/components/rt/private_include/FreeRTOS_POSIX/utils.h new file mode 100644 index 0000000000..8f748eaee4 --- /dev/null +++ b/components/rt/private_include/FreeRTOS_POSIX/utils.h @@ -0,0 +1,149 @@ +/* + * Amazon FreeRTOS+POSIX V1.0.0 + * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * SPDX-FileCopyrightText: 2018 Amazon.com, Inc. or its affiliates + * + * SPDX-License-Identifier: MIT + * + * SPDX-FileContributor: 2024 Espressif Systems (Shanghai) CO LTD + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * http://aws.amazon.com/freertos + * http://www.FreeRTOS.org + */ + +/** + * @file utils.h + * @brief Utility functions used by FreeRTOS+POSIX. + */ + +#ifndef _FREERTOS_POSIX_UTILS_ +#define _FREERTOS_POSIX_UTILS_ + +/* C standard library includes. */ +#include +#include + +#include + +#define MICROSECONDS_PER_SECOND ( 1000000LL ) /**< Microseconds per second. */ +#define NANOSECONDS_PER_SECOND ( 1000000000LL ) /**< Nanoseconds per second. */ +#define NANOSECONDS_PER_TICK ( NANOSECONDS_PER_SECOND / configTICK_RATE_HZ ) + +/** + * @brief Calculates the length of pcString, up to xMaxLength. + * + * @param[in] pcString The string to find the length of. + * @param[in] xMaxLength The limit when searching for the end of pcString. + * + * @return 0 if pcString is NULL; otherwise, the length of pcString or xMaxLength, + * whichever is smaller. + */ +size_t UTILS_strnlen( const char * const pcString, + size_t xMaxLength ); + +/** + * @brief Calculates the number of ticks between now and a given timespec. + * + * @param[in] pxAbsoluteTime A time in the future, specified as seconds and + * nanoseconds since CLOCK_REALTIME's 0. + * @param[out] pxResult Where the result of the conversion is stored. The result + * is rounded up for fractional ticks. + * + * @return 0 on success. Otherwise, ETIMEDOUT if pxAbsoluteTime is in the past, + * or EINVAL for invalid parameters. + */ +int UTILS_AbsoluteTimespecToTicks( const struct timespec * const pxAbsoluteTime, + TickType_t * const pxResult ); + +/** + * @brief Converts a struct timespec to FreeRTOS ticks. + * + * @param[in] pxTimespec The timespec to convert. + * @param[out] pxResult Where the result of the conversion is stored. The result is rounded + * up for fractional ticks. + * + * @return 0 on success. Otherwise, EINVAL for invalid parameters. + */ +int UTILS_TimespecToTicks( const struct timespec * const pxTimespec, + TickType_t * const pxResult ); + +/** + * @brief Converts an integer value to a timespec. + * + * @param[in] llSource The value to convert. + * @param[out] pxDestination Where to store the converted value. + * + * @return No return value. + */ +void UTILS_NanosecondsToTimespec( int64_t llSource, + struct timespec * const pxDestination ); + +/** + * @brief Calculates pxResult = x + y. + * + * @param[out] pxResult Where the result of the calculation is stored. + * @param[in] x The first argument for addition. + * @param[in] y The second argument for addition. + * + * @return -1 if any argument was NULL; 1 if result is negative; otherwise, 0. + */ +int UTILS_TimespecAdd( struct timespec * const pxResult, + const struct timespec * const x, + const struct timespec * const y ); + +/** + * @brief Calculates pxResult = x + ( struct timespec ) nanosec. + * + * @param[out] pxResult Where the result of the calculation is stored. + * @param[in] x The first argument for addition. + * @param[in] llNanoseconds The second argument for addition. + * + * @return -1 if pxResult or x was NULL; 1 if result is negative; otherwise, 0. + */ +int UTILS_TimespecAddNanoseconds( struct timespec * const pxResult, + const struct timespec * const x, + int64_t llNanoseconds ); + +/** + * @brief Calculates pxResult = x - y. + * + * @param[out] pxResult Where the result of the calculation is stored. + * @param[in] x The first argument for subtraction. + * @param[in] y The second argument for subtraction. + * + * @return -1 if any argument was NULL; 1 if result is negative; otherwise, 0. + */ +int UTILS_TimespecSubtract( struct timespec * const pxResult, + const struct timespec * const x, + const struct timespec * const y ); + +/** + * @brief Checks that a timespec conforms to POSIX. + * + * A valid timespec must have 0 <= tv_nsec < 1000000000. + * + * @param[in] pxTimespec The timespec to validate. + * + * @return true if the pxTimespec is valid, false otherwise. + */ +bool UTILS_ValidateTimespec( const struct timespec * const pxTimespec ); + +#endif /* ifndef _FREERTOS_POSIX_UTILS_ */ diff --git a/components/rt/private_include/FreeRTOS_POSIX_internal.h b/components/rt/private_include/FreeRTOS_POSIX_internal.h new file mode 100644 index 0000000000..ecabbfc994 --- /dev/null +++ b/components/rt/private_include/FreeRTOS_POSIX_internal.h @@ -0,0 +1,101 @@ +/* + * Amazon FreeRTOS+POSIX V1.0.0 + * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * SPDX-FileCopyrightText: 2018 Amazon.com, Inc. or its affiliates + * + * SPDX-License-Identifier: MIT + * + * SPDX-FileContributor: 2024 Espressif Systems (Shanghai) CO LTD + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * http://aws.amazon.com/freertos + * http://www.FreeRTOS.org + */ + +#ifndef _FREERTOS_POSIX_INTERNAL_H_ +#define _FREERTOS_POSIX_INTERNAL_H_ + +/** + * @file FreeRTOS_POSIX_internal.h + * @brief Internal structs and initializers for FreeRTOS+POSIX. + */ + +/* Amazon FreeRTOS includes. */ +#include "aws_doubly_linked_list.h" + +/** + * @brief Mutex attribute object. + */ +typedef struct pthread_mutexattr_internal +{ + int iType; /**< Mutex type. */ +} pthread_mutexattr_internal_t; + +/** + * @brief Mutex. + */ +typedef struct pthread_mutex_internal +{ + BaseType_t xIsInitialized; /**< Set to pdTRUE if this mutex is initialized, pdFALSE otherwise. */ + StaticSemaphore_t xMutex; /**< FreeRTOS mutex. */ + TaskHandle_t xTaskOwner; /**< Owner; used for deadlock detection and permission checks. */ + pthread_mutexattr_internal_t xAttr; /**< Mutex attributes. */ +} pthread_mutex_internal_t; + +/** + * @brief Compile-time initializer of pthread_mutex_internal_t. + */ +#define FREERTOS_POSIX_MUTEX_INITIALIZER \ + ( &( ( pthread_mutex_internal_t ) \ + { \ + .xIsInitialized = pdFALSE, \ + .xMutex = { { 0 } }, \ + .xTaskOwner = NULL, \ + .xAttr = { .iType = 0 } \ + } \ + ) \ + ) + +/** + * @brief Condition variable. + */ +typedef struct pthread_cond_internal +{ + BaseType_t xIsInitialized; /**< Set to pdTRUE if this condition variable is initialized, pdFALSE otherwise. */ + StaticSemaphore_t xCondMutex; /**< Prevents concurrent accesses to iWaitingThreads. */ + StaticSemaphore_t xCondWaitSemaphore; /**< Threads block on this semaphore in pthread_cond_wait. */ + int iWaitingThreads; /**< The number of threads currently waiting on this condition variable. */ +} pthread_cond_internal_t; + +/** + * @brief Compile-time initializer of pthread_cond_internal_t. + */ +#define FREERTOS_POSIX_COND_INITIALIZER \ + ( &( ( pthread_cond_internal_t ) \ + { \ + .xIsInitialized = pdFALSE, \ + .xCondMutex = { { 0 } }, \ + .xCondWaitSemaphore = { { 0 } }, \ + .iWaitingThreads = 0 \ + } \ + ) \ + ) + +#endif /* _FREERTOS_POSIX_INTERNAL_H_ */ diff --git a/components/rt/private_include/FreeRTOS_POSIX_portable.h b/components/rt/private_include/FreeRTOS_POSIX_portable.h new file mode 100644 index 0000000000..291f72f571 --- /dev/null +++ b/components/rt/private_include/FreeRTOS_POSIX_portable.h @@ -0,0 +1,59 @@ +/* + * Amazon FreeRTOS+POSIX V1.0.0 + * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * SPDX-FileCopyrightText: 2018 Amazon.com, Inc. or its affiliates + * + * SPDX-License-Identifier: MIT + * + * SPDX-FileContributor: 2024 Espressif Systems (Shanghai) CO LTD + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * http://aws.amazon.com/freertos + * http://www.FreeRTOS.org + */ + +/** + * @file FreeRTOS_POSIX_portable.h + * @brief Port-specific configuration of FreeRTOS+POSIX. + */ + +#ifndef _FREERTOS_POSIX_PORTABLE_H_ +#define _FREERTOS_POSIX_PORTABLE_H_ + +/* ESP-IDF already defines the following types. */ +#define posixconfigENABLE_CLOCK_T 0 +#define posixconfigENABLE_CLOCKID_T 0 +#define posixconfigENABLE_MODE_T 0 +#define posixconfigENABLE_PTHREAD_ATTR_T 0 +#define posixconfigENABLE_PTHREAD_COND_T 0 +#define posixconfigENABLE_PTHREAD_CONDATTR_T 0 +#define posixconfigENABLE_PTHREAD_MUTEX_T 0 +#define posixconfigENABLE_PTHREAD_MUTEXATTR_T 0 +#define posixconfigENABLE_PTHREAD_T 0 +#define posixconfigENABLE_TIME_T 0 +#define posixconfigENABLE_TIMER_T 0 +#define posixconfigENABLE_TIMESPEC 0 +#define posixconfigENABLE_ITIMERSPEC 0 + +/* ESP-IDF already provides the header sched.h. Exclude them by + * activating the double inclusion guards. */ +#define _FREERTOS_POSIX_SCHED_H_ + +#endif /* _FREERTOS_POSIX_PORTABLE_H_ */ diff --git a/components/rt/private_include/aws_doubly_linked_list.h b/components/rt/private_include/aws_doubly_linked_list.h new file mode 100644 index 0000000000..9e5a0b347b --- /dev/null +++ b/components/rt/private_include/aws_doubly_linked_list.h @@ -0,0 +1,249 @@ +/* + * Amazon FreeRTOS + * Copyright (C) 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * SPDX-FileCopyrightText: 2018 Amazon.com, Inc. or its affiliates + * + * SPDX-License-Identifier: MIT + * + * SPDX-FileContributor: 2024 Espressif Systems (Shanghai) CO LTD + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * http://aws.amazon.com/freertos + * http://www.FreeRTOS.org + */ + + +/** + * @file aws_doubly_linked_list.h + * @brief Doubly Linked List implementation. + * + * A generic implementation of circular Doubly Linked List which consists of a + * list head and some list entries (zero in case of an empty list). + * + * To start with, a structure of type Link_t should be embedded in the structure + * which is to be organized as doubly linked list. + * @code + * typedef struct UserStruct + * { + * uint32_t ulField1; + * uint32_t ulField2; + * Link_t xLink; + * } UserStruct_t; + * @endcode + * + * A List head should then be defined and initialized. + * @code + * Link_t xListHead; + * listINIT_HEAD( &xListHead ); + * @endcode + * + * listADD can then be used to add nodes to the list. + * @code + * listADD( &( xListHead ), &( pxUserStruct->xLink ) ); + * @endcode + * + * listFOR_EACH can be used for traversing the list. + * @code + * Link_t *pxLink; + * UserStruct_t *pxUserStruct; + * listFOR_EACH( pxLink, &( xListHead ) ) + * { + * pxUserStruct = listCONTAINER( pxLink, UserStruct_t, xLink ); + * } + * @endcode + * + * listFOR_EACH_SAFE should be used if you want to perform destructive operations + * (like free) on nodes while traversing the list. + * @code + * Link_t *pxLink, *pxTempLink; + * UserStruct_t *pxUserStruct; + * listFOR_EACH( pxLink, pxTempLink, &( xListHead ) ) + * { + * pxUserStruct = listCONTAINER( pxLink, UserStruct_t, xLink ); + * free( pxUserStruct ); + * } + * @endcode + */ + +#ifndef _AWS_DOUBLY_LINKED_LIST_H_ +#define _AWS_DOUBLY_LINKED_LIST_H_ + +#include +#include + +/** + * @brief Struct embedded in any struct to make it a doubly linked + * list. + * + * pxNext in the head points to the first node in the list and pxPrev + * in the head points to the last node in the list. In case of empty + * list, both pxPrev and pxNext in the head point to the head node itself. + */ +typedef struct Link +{ + struct Link * pxPrev; /**< Pointer to the previous node. */ + struct Link * pxNext; /**< Pointer to the next node. */ +} Link_t; + +/** + * @brief Initializes the given list head to an empty list. + * + * @param[in] pxHead The given list head to initialize. + */ +#define listINIT_HEAD( pxHead ) \ + { \ + ( pxHead )->pxPrev = ( pxHead ); \ + ( pxHead )->pxNext = ( pxHead ); \ + } + +/** + * @brief Adds the given new node to the given list. + * + * @param[in] pxHead The head of the given list. + * @param[in] pxLink The given new node to be added to the given + * list. + */ +#define listADD( pxHead, pxLink ) \ + { \ + Link_t * pxPrevLink = ( pxHead ); \ + Link_t * pxNextLink = ( ( pxHead )->pxNext ); \ + \ + ( pxLink )->pxNext = pxNextLink; \ + pxNextLink->pxPrev = ( pxLink ); \ + pxPrevLink->pxNext = ( pxLink ); \ + ( pxLink )->pxPrev = ( pxPrevLink ); \ + } + +/** + * @brief Removes the given node from the list it is part of. + * + * If the given node is not a part of any list (i.e. next and previous + * nodes are NULL), nothing happens. + * + * @param[in] pxLink The given node to remove from the list. + */ +#define listREMOVE( pxLink ) \ + { \ + /* If the link is part of a list, remove it from the list. */ \ + if( ( pxLink )->pxNext != NULL && ( pxLink )->pxPrev != NULL ) \ + { \ + ( pxLink )->pxPrev->pxNext = ( pxLink )->pxNext; \ + ( pxLink )->pxNext->pxPrev = ( pxLink )->pxPrev; \ + } \ + \ + /* Make sure that this link is not part of any list anymore. */ \ + ( pxLink )->pxPrev = NULL; \ + ( pxLink )->pxNext = NULL; \ + } + +/** + * @brief Given the head of a list, checks if the list is empty. + * + * @param[in] pxHead The head of the given list. + */ +#define listIS_EMPTY( pxHead ) ( ( ( pxHead ) == NULL ) || ( ( pxHead )->pxNext == ( pxHead ) ) ) + +/** + * @brief Removes the first node from the given list and returns it. + * + * Removes the first node from the given list and assigns it to the + * pxLink parameter. If the list is empty, it assigns NULL to the + * pxLink. + * + * @param[in] pxHead The head of the list from which to remove the + * first node. + * @param[out] pxLink The output parameter to receive the removed + * node. + */ +#define listPOP( pxHead, pxLink ) \ + { \ + if( listIS_EMPTY( ( pxHead ) ) ) \ + { \ + ( pxLink ) = NULL; \ + } \ + else \ + { \ + ( pxLink ) = ( pxHead )->pxNext; \ + /* If the link is part of a list, remove it from the list. */ \ + if( ( pxLink )->pxNext != NULL && ( pxLink )->pxPrev != NULL ) \ + { \ + ( pxLink )->pxPrev->pxNext = ( pxLink )->pxNext; \ + ( pxLink )->pxNext->pxPrev = ( pxLink )->pxPrev; \ + } \ + \ + /* Make sure that this link is not part of any list anymore. */ \ + ( pxLink )->pxPrev = NULL; \ + ( pxLink )->pxNext = NULL; \ + } \ + } + +/** + * @brief Merges a list into a given list. + * + * @param[in] pxHeadResultList The head of the given list into which the + * other list should be merged. + * @param[in] pxHeadListToMerge The head of the list to be merged into the + * given list. + */ +#define listMERGE( pxHeadResultList, pxHeadListToMerge ) \ + { \ + if( !listIS_EMPTY( ( pxHeadListToMerge ) ) ) \ + { \ + /* Setup links between last node of listToMerge and first node of resultList. */ \ + ( pxHeadListToMerge )->pxPrev->pxNext = ( pxHeadResultList )->pxNext; \ + ( pxHeadResultList )->pxNext->pxPrev = ( pxHeadListToMerge )->pxPrev; \ + \ + /* Setup links between first node of listToMerge and the head of resultList. */ \ + ( pxHeadListToMerge )->pxNext->pxPrev = ( pxHeadResultList ); \ + ( pxHeadResultList )->pxNext = ( pxHeadListToMerge )->pxNext; \ + /* Empty the merged list. */ \ + listINIT_HEAD( ( pxHeadListToMerge ) ); \ + } \ + } + +/** + * @brief Helper macro to iterate over a list. pxLink contains the link node + * in each iteration. + */ +#define listFOR_EACH( pxLink, pxHead ) \ + for( ( pxLink ) = ( pxHead )->pxNext; \ + ( pxLink ) != ( pxHead ); \ + ( pxLink ) = ( pxLink )->pxNext ) + +/** + * @brief Helper macro to iterate over a list. It is safe to destroy/free the + * nodes while iterating. pxLink contains the link node in each iteration. + */ +#define listFOR_EACH_SAFE( pxLink, pxTempLink, pxHead ) \ + for( ( pxLink ) = ( pxHead )->pxNext, ( pxTempLink ) = ( pxLink )->pxNext; \ + ( pxLink ) != ( pxHead ); \ + ( pxLink ) = ( pxTempLink ), ( pxTempLink ) = ( pxLink )->pxNext ) + +/** + * @brief Given the pointer to the link member (of type Link_t) in a struct, + * extracts the pointer to the containing struct. + * + * @param[in] pxLink The pointer to the link member. + * @param[in] type The type of the containing struct. + * @param[in] member Name of the link member in the containing struct. + */ +#define listCONTAINER( pxLink, type, member ) ( ( type * ) ( ( uint8_t * ) ( pxLink ) - ( uint8_t * ) ( &( ( type * ) 0 )->member ) ) ) + +#endif /* _AWS_DOUBLY_LINKED_LIST_H_ */ diff --git a/components/rt/private_include/portable/FreeRTOS_POSIX_portable_default.h b/components/rt/private_include/portable/FreeRTOS_POSIX_portable_default.h new file mode 100644 index 0000000000..9cc2ba0fdf --- /dev/null +++ b/components/rt/private_include/portable/FreeRTOS_POSIX_portable_default.h @@ -0,0 +1,144 @@ +/* + * Amazon FreeRTOS+POSIX V1.0.0 + * Copyright (C) 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * SPDX-FileCopyrightText: 2018 Amazon.com, Inc. or its affiliates + * + * SPDX-License-Identifier: MIT + * + * SPDX-FileContributor: 2024 Espressif Systems (Shanghai) CO LTD + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * http://aws.amazon.com/freertos + * http://www.FreeRTOS.org + */ + +/** + * @file FreeRTOS_POSIX_portable_default.h + * @brief Defaults for port-specific configuration of FreeRTOS+POSIX. + */ + +#ifndef _FREERTOS_POSIX_PORTABLE_DEFAULT_H_ +#define _FREERTOS_POSIX_PORTABLE_DEFAULT_H_ + +/** + * @brief The FreeRTOS task name given to pthreads. + */ +#ifndef posixconfigPTHREAD_TASK_NAME + #define posixconfigPTHREAD_TASK_NAME "pthread" +#endif + +/** + * @brief the FreeRTOS timer name given to POSIX timers. + */ +#ifndef posixconfigTIMER_NAME + #define posixconfigTIMER_NAME "timer" +#endif + +/** + * @defgroup Defaults for POSIX message queue implementation. + */ +/**@{ */ +#ifndef posixconfigMQ_MAX_MESSAGES + #define posixconfigMQ_MAX_MESSAGES 10 /**< Maximum number of messages in an mq at one time. */ +#endif + +#ifndef posixconfigMQ_MAX_SIZE + #define posixconfigMQ_MAX_SIZE 128 /**< Maximum size (in bytes) of each message. */ +#endif +/**@} */ + +/** + * @defgroup POSIX implementation-dependent constants usually defined in limits.h. + * + * They are defined here to provide portability between platforms. + */ +/**@{ */ +#ifndef PTHREAD_STACK_MIN + #define PTHREAD_STACK_MIN configMINIMAL_STACK_SIZE * sizeof( StackType_t ) /**< Minimum size in bytes of thread stack storage. */ +#endif +#ifndef NAME_MAX + #define NAME_MAX 64 /**< Maximum number of bytes in a filename (not including terminating null). */ +#endif +#ifndef SEM_VALUE_MAX + #define SEM_VALUE_MAX 0xFFFFU /**< Maximum value of a sem_t. */ +#endif +/**@} */ + +/** + * @defgroup Enable typedefs of POSIX types. + * + * Set these values to 1 or 0 to enable or disable the typedefs, respectively. + * These typedefs should only be disabled if they conflict with system typedefs. + */ +/**@{ */ +#ifndef posixconfigENABLE_CLOCK_T + #define posixconfigENABLE_CLOCK_T 1 /**< clock_t in sys/types.h */ +#endif +#ifndef posixconfigENABLE_CLOCKID_T + #define posixconfigENABLE_CLOCKID_T 1 /**< clockid_t in sys/types.h */ +#endif +#ifndef posixconfigENABLE_MODE_T + #define posixconfigENABLE_MODE_T 1 /**< mode_t in sys/types.h */ +#endif +#ifndef posixconfigENABLE_PID_T + #define posixconfigENABLE_PID_T 1 /**< pid_t in sys/types.h */ +#endif +#ifndef posixconfigENABLE_PTHREAD_ATTR_T + #define posixconfigENABLE_PTHREAD_ATTR_T 1 /**< pthread_attr_t in sys/types.h */ +#endif +#ifndef posixconfigENABLE_PTHREAD_COND_T + #define posixconfigENABLE_PTHREAD_COND_T 1 /**< pthread_cond_t in sys/types.h */ +#endif +#ifndef posixconfigENABLE_PTHREAD_CONDATTR_T + #define posixconfigENABLE_PTHREAD_CONDATTR_T 1 /**< pthread_condattr_t in sys/types.h */ +#endif +#ifndef posixconfigENABLE_PTHREAD_MUTEX_T + #define posixconfigENABLE_PTHREAD_MUTEX_T 1 /**< pthread_mutex_t in sys/types.h */ +#endif +#ifndef posixconfigENABLE_PTHREAD_MUTEXATTR_T + #define posixconfigENABLE_PTHREAD_MUTEXATTR_T 1 /**< pthread_mutexattr_t in sys/types.h */ +#endif +#ifndef posixconfigENABLE_PTHREAD_T + #define posixconfigENABLE_PTHREAD_T 1 /**< pthread_t in sys/types.h */ +#endif +#ifndef posixconfigENABLE_SSIZE_T + #define posixconfigENABLE_SSIZE_T 1 /**< ssize_t in sys/types.h */ +#endif +#ifndef posixconfigENABLE_TIME_T + #define posixconfigENABLE_TIME_T 1 /**< time_t in sys/types.h */ +#endif +#ifndef posixconfigENABLE_TIMER_T + #define posixconfigENABLE_TIMER_T 1 /**< timer_t in sys/types.h */ +#endif +#ifndef posixconfigENABLE_USECONDS_T + #define posixconfigENABLE_USECONDS_T 1 /**< useconds_t in sys/types.h */ +#endif +#ifndef posixconfigENABLE_TIMESPEC + #define posixconfigENABLE_TIMESPEC 1 /**< struct timespec in time.h */ +#endif +#ifndef posixconfigENABLE_ITIMERSPEC + #define posixconfigENABLE_ITIMERSPEC 1 /**< struct itimerspec in time.h */ +#endif +#ifndef posixconfigENABLE_TM + #define posixconfigENABLE_TM 1 /**< struct tm in time.h */ +#endif +/**@} */ + +#endif /* ifndef _FREERTOS_POSIX_PORTABLE_DEFAULT_H_ */ diff --git a/components/rt/sbom.yml b/components/rt/sbom.yml new file mode 100644 index 0000000000..4fc6e88bb8 --- /dev/null +++ b/components/rt/sbom.yml @@ -0,0 +1,5 @@ +name: 'FreeRTOS-Plus-POSIX' +version: '1.0.0' +supplier: 'Organization: Espressif Systems (Shanghai) CO LTD' +originator: 'Organization: Amazon Web Services' +description: Portable Operating System Interface for FreeRTOS diff --git a/components/rt/test_apps/posix_rt_test/.build-test-rules.yml b/components/rt/test_apps/posix_rt_test/.build-test-rules.yml new file mode 100644 index 0000000000..102749b198 --- /dev/null +++ b/components/rt/test_apps/posix_rt_test/.build-test-rules.yml @@ -0,0 +1,9 @@ +# Documentation: .gitlab/ci/README.md#manifest-file-to-control-the-buildtest-apps + +components/rt/test_apps/posix_rt_test: + enable: + - if: IDF_TARGET in ["esp32", "esp32s2", "esp32c3", "esp32p4"] + reason: covers all major arch types, xtensa vs riscv, single vs dual-core + depends_components: + - rt + - freertos diff --git a/components/rt/test_apps/posix_rt_test/CMakeLists.txt b/components/rt/test_apps/posix_rt_test/CMakeLists.txt new file mode 100644 index 0000000000..99869dce0a --- /dev/null +++ b/components/rt/test_apps/posix_rt_test/CMakeLists.txt @@ -0,0 +1,7 @@ +# The following five lines of boilerplate have to be in your project's +# CMakeLists in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.16) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +set(COMPONENTS main) +project(posix_test) diff --git a/components/rt/test_apps/posix_rt_test/README.md b/components/rt/test_apps/posix_rt_test/README.md new file mode 100644 index 0000000000..487019d724 --- /dev/null +++ b/components/rt/test_apps/posix_rt_test/README.md @@ -0,0 +1,2 @@ +| Supported Targets | ESP32 | ESP32-C3 | ESP32-P4 | ESP32-S2 | +| ----------------- | ----- | -------- | -------- | -------- | diff --git a/components/rt/test_apps/posix_rt_test/main/CMakeLists.txt b/components/rt/test_apps/posix_rt_test/main/CMakeLists.txt new file mode 100644 index 0000000000..acc04d35e5 --- /dev/null +++ b/components/rt/test_apps/posix_rt_test/main/CMakeLists.txt @@ -0,0 +1,4 @@ +idf_component_register(SRCS "main.c" "mqueue_test.cpp" + INCLUDE_DIRS "." + PRIV_REQUIRES rt unity + WHOLE_ARCHIVE) diff --git a/components/rt/test_apps/posix_rt_test/main/main.c b/components/rt/test_apps/posix_rt_test/main/main.c new file mode 100644 index 0000000000..88bb713065 --- /dev/null +++ b/components/rt/test_apps/posix_rt_test/main/main.c @@ -0,0 +1,36 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +#include +#include +#include "unity.h" +#include "unity_test_runner.h" +#include "unity_test_utils_memory.h" +#include "esp_log.h" + +static const char *TAG = "Linux_sim"; + +void setUp(void) +{ + unity_utils_set_leak_level(0); + unity_utils_record_free_mem(); +} + +void tearDown(void) +{ + unity_utils_evaluate_leaks(); +} + +void app_main(void) +{ + // The following code avoids memory leaks from initializing libc structures in clock_gettime() calls. + struct timespec tp; + clock_gettime(CLOCK_REALTIME, &tp); + (void) tp; + + ESP_LOGI(TAG, "Running FreeRTOS Linux test app"); + unity_run_menu(); +} diff --git a/components/rt/test_apps/posix_rt_test/main/mqueue_test.cpp b/components/rt/test_apps/posix_rt_test/main/mqueue_test.cpp new file mode 100644 index 0000000000..a9876d78f7 --- /dev/null +++ b/components/rt/test_apps/posix_rt_test/main/mqueue_test.cpp @@ -0,0 +1,288 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +#include +#include +#include +#include +#include "unity.h" +#include "mqueue.h" + +// Note: implementation does not support mode argument +// Note: implementation does not support message priorities (msg_prio arguments) + +struct MessageQueueFixture { + MessageQueueFixture(const char *name = "/test_queue", + long int maxmsg = 10, + long int msgsize = sizeof(int), + int oflag = O_CREAT | O_WRONLY) + : name(name) { + struct mq_attr conf = { + .mq_flags = 0, // ignored by mq_open + .mq_maxmsg = maxmsg, + .mq_msgsize = msgsize, + .mq_curmsgs = 0 // ignored by mq_open + }; + mq = mq_open(name, oflag, S_IRUSR, &conf); + TEST_ASSERT_NOT_EQUAL((mqd_t) -1, mq); + } + ~MessageQueueFixture() { + TEST_ASSERT_EQUAL(0, mq_close(mq)); + TEST_ASSERT_EQUAL(0, mq_unlink(name)); + } + const char *name; + mqd_t mq; +}; + +TEST_CASE("open with invalid message queue name fails", "[mqueue]") +{ + struct mq_attr conf = { + .mq_flags = 0, // ignored by mq_open + .mq_maxmsg = 10, + .mq_msgsize = sizeof(int), + .mq_curmsgs = 0 // ignored by mq_open + }; + +// 255 + 2 characters, excl. '\0' +#define TOO_LONG_NAME "/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" \ + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" \ + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" \ + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" \ + + TEST_ASSERT_EQUAL((mqd_t) -1, mq_open("", O_CREAT | O_WRONLY, S_IRUSR | S_IWUSR , &conf)); + TEST_ASSERT_EQUAL(EINVAL, errno); + TEST_ASSERT_EQUAL((mqd_t) -1, mq_open("my_queue", O_CREAT | O_WRONLY, S_IRUSR | S_IWUSR , &conf)); + TEST_ASSERT_EQUAL(EINVAL, errno); + TEST_ASSERT_EQUAL((mqd_t) -1, mq_open(TOO_LONG_NAME, O_CREAT | O_WRONLY, S_IRUSR | S_IWUSR , &conf)); + TEST_ASSERT_EQUAL(EINVAL, errno); +} + +TEST_CASE("name / is valid", "[mqueue]") +{ + mqd_t mq = mq_open("/", O_CREAT | O_WRONLY, S_IRUSR, NULL); + + TEST_ASSERT_NOT_EQUAL((mqd_t) -1, mq); + + TEST_ASSERT_EQUAL(0, mq_close(mq)); + TEST_ASSERT_EQUAL(0, mq_unlink("/")); +} + +TEST_CASE("open without create flag fails", "[mqueue]") +{ + TEST_ASSERT_EQUAL((mqd_t) -1, mq_open("/my_queue", O_WRONLY)); + TEST_ASSERT_EQUAL(ENOENT, errno); +} + +TEST_CASE("open mq_maxmsg <= 0", "[mqueue]") +{ + struct mq_attr conf = { + .mq_flags = 0, // ignored by mq_open + .mq_maxmsg = 0, + .mq_msgsize = sizeof(int), + .mq_curmsgs = 0 // ignored by mq_open + }; + + TEST_ASSERT_EQUAL((mqd_t) -1, mq_open("/my_queue", O_CREAT | O_WRONLY, S_IRUSR | S_IWUSR , &conf)); + TEST_ASSERT_EQUAL(EINVAL, errno); + + conf.mq_maxmsg = -1; + + TEST_ASSERT_EQUAL((mqd_t) -1, mq_open("/my_queue", O_CREAT | O_WRONLY, S_IRUSR | S_IWUSR , &conf)); + TEST_ASSERT_EQUAL(EINVAL, errno); +} + +TEST_CASE("open mq_msgsize <= 0", "[mqueue]") +{ + struct mq_attr conf = { + .mq_flags = 0, // ignored by mq_open + .mq_maxmsg = 10, + .mq_msgsize = 0, + .mq_curmsgs = 0 // ignored by mq_open + }; + + TEST_ASSERT_EQUAL((mqd_t) -1, mq_open("/my_queue", O_CREAT | O_WRONLY, S_IRUSR | S_IWUSR , &conf)); + TEST_ASSERT_EQUAL(EINVAL, errno); + + conf.mq_msgsize = -1; + + TEST_ASSERT_EQUAL((mqd_t) -1, mq_open("/my_queue", O_CREAT | O_WRONLY, S_IRUSR | S_IWUSR , &conf)); + TEST_ASSERT_EQUAL(EINVAL, errno); +} + +TEST_CASE("creating already existing message queue fails", "[mqueue]") +{ + MessageQueueFixture mq_fix; + + TEST_ASSERT_EQUAL((mqd_t) -1, mq_open(mq_fix.name, O_CREAT | O_WRONLY | O_EXCL, S_IRUSR | S_IWUSR , NULL)); + TEST_ASSERT_EQUAL(EEXIST, errno); +} + +TEST_CASE("open with default flags", "[mqueue]") +{ + mqd_t mq = mq_open("/my_queue", O_CREAT | O_WRONLY, S_IRUSR, NULL); + TEST_ASSERT_NOT_EQUAL((mqd_t) -1, mq); + + struct mq_attr default_conf; + TEST_ASSERT_EQUAL(0, mq_getattr(mq, &default_conf)); + TEST_ASSERT_EQUAL(10, default_conf.mq_maxmsg); + TEST_ASSERT_EQUAL(128, default_conf.mq_msgsize); + + TEST_ASSERT_EQUAL(0, mq_close(mq)); + TEST_ASSERT_EQUAL(0, mq_unlink("/my_queue")); +} + +TEST_CASE("open propagates attributes correctly", "[mqueue]") +{ + MessageQueueFixture mq_fix("/test", 4, 8); + + struct mq_attr conf; + TEST_ASSERT_EQUAL(0, mq_getattr(mq_fix.mq, &conf)); + TEST_ASSERT_EQUAL(4, conf.mq_maxmsg); + TEST_ASSERT_EQUAL(8, conf.mq_msgsize); +} + +TEST_CASE("message queue does not exceed queue size", "[mqueue]") +{ + MessageQueueFixture mq_fix("/test", 1, sizeof(int)); + int to_send = 47; + TEST_ASSERT_EQUAL(0, mq_send(mq_fix.mq, (const char*) &to_send, sizeof(to_send), 0)); + int to_send_2 = 48; + int received = 0; + + const struct timespec zero_time = {0, 0}; + TEST_ASSERT_EQUAL(-1, mq_timedsend(mq_fix.mq, (const char*) &to_send_2, sizeof(to_send_2), 0, &zero_time)); + TEST_ASSERT_EQUAL(ETIMEDOUT, errno); + + TEST_ASSERT_EQUAL(4, mq_receive(mq_fix.mq, (char*) &received, sizeof(received), nullptr)); + TEST_ASSERT_EQUAL(47, received); +} + +TEST_CASE("sending on invalid mq descriptor fails", "[mqueue]") +{ + int to_send = 47; + TEST_ASSERT_EQUAL(-1, mq_send(nullptr, (char*) &to_send, sizeof(to_send), 0)); + TEST_ASSERT_EQUAL(EBADF, errno); +} + +TEST_CASE("sending with invalid timeout fails", "[mqueue]") +{ + MessageQueueFixture mq_fix("/test", 1, sizeof(int)); + int to_send = 47; + struct timespec invalid_time = {0, 1'000'000'000}; + TEST_ASSERT_EQUAL(-1, mq_timedsend(mq_fix.mq, (char*) &to_send, sizeof(to_send), 0, &invalid_time)); + TEST_ASSERT_EQUAL(EINVAL, errno); + invalid_time.tv_nsec = -1; + TEST_ASSERT_EQUAL(-1, mq_timedsend(mq_fix.mq, (char*) &to_send, sizeof(to_send), 0, &invalid_time)); + TEST_ASSERT_EQUAL(EINVAL, errno); +} + +TEST_CASE("sending with invalid message size fails", "[mqueue]") +{ + MessageQueueFixture mq_fix("/test", 1, sizeof(int)); + int to_send = 47; + TEST_ASSERT_EQUAL(-1, mq_send(mq_fix.mq, (char*) &to_send, sizeof(to_send) + 1, 0)); + TEST_ASSERT_EQUAL(EMSGSIZE, errno); +} + +TEST_CASE("sending does not exceed queue size", "[mqueue]") +{ + MessageQueueFixture mq_fix("/test", 1, sizeof(int)); + int to_send = 47; + TEST_ASSERT_EQUAL(0, mq_send(mq_fix.mq, (const char*) &to_send, sizeof(to_send), 0)); + int to_send_2 = 48; + int received = 0; + + const struct timespec zero_time = {0, 0}; + TEST_ASSERT_EQUAL(-1, mq_timedsend(mq_fix.mq, (const char*) &to_send_2, sizeof(to_send_2), 0, &zero_time)); + TEST_ASSERT_EQUAL(ETIMEDOUT, errno); + + TEST_ASSERT_EQUAL(4, mq_receive(mq_fix.mq, (char*) &received, sizeof(received), nullptr)); + TEST_ASSERT_EQUAL(47, received); +} + +TEST_CASE("nonblocking send works", "[mqueue]") +{ + MessageQueueFixture mq_fix("/test", 1, sizeof(int), O_CREAT | O_WRONLY | O_NONBLOCK); + int to_send = 47; + int received = 0; + TEST_ASSERT_EQUAL(0, mq_send(mq_fix.mq, (char*) &to_send, sizeof(to_send), 0)); + int to_send_2 = 48; + const struct timespec zero_time = {0, 0}; + + TEST_ASSERT_EQUAL(-1, mq_timedsend(mq_fix.mq, (char*) &to_send_2, sizeof(to_send_2), 0, &zero_time)); + TEST_ASSERT_EQUAL(EAGAIN, errno); + TEST_ASSERT_EQUAL(-1, mq_send(mq_fix.mq, (char*) &to_send_2, sizeof(to_send_2), 0)); + TEST_ASSERT_EQUAL(EAGAIN, errno); + + TEST_ASSERT_EQUAL(4, mq_receive(mq_fix.mq, (char*) &received, sizeof(received), nullptr)); + TEST_ASSERT_EQUAL(47, received); +} + +TEST_CASE("receiving on invalid mq descriptor fails", "[mqueue]") +{ + int received; + TEST_ASSERT_EQUAL(-1, mq_receive(nullptr, (char*) &received, sizeof(received), nullptr)); + TEST_ASSERT_EQUAL(EBADF, errno); +} + +TEST_CASE("receiving with invalid timeout fails", "[mqueue]") +{ + MessageQueueFixture mq_fix("/test", 1, sizeof(int)); + int received; + struct timespec invalid_time = {0, 1'000'000'000}; + TEST_ASSERT_EQUAL(-1, mq_timedreceive(mq_fix.mq, (char*) &received, sizeof(received), 0, &invalid_time)); + TEST_ASSERT_EQUAL(EINVAL, errno); + invalid_time.tv_nsec = -1; + TEST_ASSERT_EQUAL(-1, mq_timedreceive(mq_fix.mq, (char*) &received, sizeof(received), 0, &invalid_time)); + TEST_ASSERT_EQUAL(EINVAL, errno); +} + +TEST_CASE("receiving with invalid message size fails", "[mqueue]") +{ + MessageQueueFixture mq_fix("/test", 1, sizeof(int)); + int received; + TEST_ASSERT_EQUAL(-1, mq_send(mq_fix.mq, (char*) &received, sizeof(received) + 1, 0)); + TEST_ASSERT_EQUAL(EMSGSIZE, errno); +} + +TEST_CASE("receive on empty message queue times out", "[mqueue]") +{ + MessageQueueFixture mq_fix("/test", 1, sizeof(int)); + int received; + const struct timespec zero_time = {0, 0}; + + TEST_ASSERT_EQUAL(-1, mq_timedreceive(mq_fix.mq, (char*) &received, sizeof(received), 0, &zero_time)); + TEST_ASSERT_EQUAL(ETIMEDOUT, errno); +} + +TEST_CASE("nonblocking receive works", "[mqueue]") +{ + MessageQueueFixture mq_fix("/test", 1, sizeof(int), O_CREAT | O_WRONLY | O_NONBLOCK); + int received; + const struct timespec zero_time = {0, 0}; + + TEST_ASSERT_EQUAL(-1, mq_timedreceive(mq_fix.mq, (char*) &received, sizeof(received), 0, &zero_time)); + TEST_ASSERT_EQUAL(EAGAIN, errno); + TEST_ASSERT_EQUAL(-1, mq_receive(mq_fix.mq, (char*) &received, sizeof(received), nullptr)); + TEST_ASSERT_EQUAL(EAGAIN, errno); +} + +TEST_CASE("queue sends and receives multiple messages", "[mqueue]") +{ + MessageQueueFixture mq_fix("/test", 2, sizeof(int), O_CREAT | O_WRONLY | O_NONBLOCK); + int received; + int to_send = 47; + + TEST_ASSERT_EQUAL(0, mq_send(mq_fix.mq, (char*) &to_send, sizeof(to_send), 0)); + to_send = 48; + TEST_ASSERT_EQUAL(0, mq_send(mq_fix.mq, (char*) &to_send, sizeof(to_send), 0)); + + TEST_ASSERT_EQUAL(4, mq_receive(mq_fix.mq, (char*) &received, sizeof(received), nullptr)); + TEST_ASSERT_EQUAL(47, received); + TEST_ASSERT_EQUAL(4, mq_receive(mq_fix.mq, (char*) &received, sizeof(received), nullptr)); + TEST_ASSERT_EQUAL(48, received); + +} diff --git a/components/rt/test_apps/posix_rt_test/pytest_rt_mqueue_tests.py b/components/rt/test_apps/posix_rt_test/pytest_rt_mqueue_tests.py new file mode 100644 index 0000000000..f43f8abc22 --- /dev/null +++ b/components/rt/test_apps/posix_rt_test/pytest_rt_mqueue_tests.py @@ -0,0 +1,13 @@ +# SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: CC0-1.0 +import pytest +from pytest_embedded import Dut + + +@pytest.mark.generic +@pytest.mark.esp32 +@pytest.mark.esp32c3 +@pytest.mark.esp32p4 +@pytest.mark.esp32s2 +def test_rt_mqueue(dut: Dut) -> None: + dut.run_all_single_board_cases() diff --git a/components/rt/test_apps/posix_rt_test/sdkconfig.defaults b/components/rt/test_apps/posix_rt_test/sdkconfig.defaults new file mode 100644 index 0000000000..e510020a06 --- /dev/null +++ b/components/rt/test_apps/posix_rt_test/sdkconfig.defaults @@ -0,0 +1,2 @@ +# This "default" configuration is appended to all other configurations +CONFIG_ESP_TASK_WDT_INIT=n diff --git a/docs/en/api-reference/system/pthread.rst b/docs/en/api-reference/system/pthread.rst index 544fb1ff94..f6314ebbd8 100644 --- a/docs/en/api-reference/system/pthread.rst +++ b/docs/en/api-reference/system/pthread.rst @@ -1,5 +1,5 @@ -POSIX Threads Support -===================== +POSIX Support (Including POSIX Threads Support) +=============================================== :link_to_translation:`zh_CN:[中文]` @@ -12,7 +12,11 @@ POSIX Threads are implemented in ESP-IDF as wrappers around equivalent FreeRTOS Pthreads can be used in ESP-IDF by including standard ``pthread.h`` header, which is included in the toolchain libc. An additional ESP-IDF specific header, ``esp_pthread.h``, provides additional non-POSIX APIs for using some ESP-IDF features with pthreads. -C++ Standard Library implementations for ``std::thread``, ``std::mutex``, ``std::condition_variable``, etc., are realized using pthreads (via GCC libstdc++). Therefore, restrictions mentioned here also apply to the equivalent C++ standard library functionality. +Besides POSIX Threads, ESP-IDF also supports :ref:`POSIX message queues `. + +C++ Standard Library implementations for ``std::thread``, ``std::mutex``, ``std::condition_variable``, etc., are realized using pthreads and other POSIX APIs (via GCC libstdc++). Therefore, restrictions mentioned here also apply to the equivalent C++ standard library functionality. + +If you identify a useful API that you would like to see implemented in ESP-IDF, please open a `feature request on GitHub `_ with the details. RTOS Integration ---------------- @@ -23,6 +27,10 @@ Unlike many operating systems using POSIX Threads, ESP-IDF is a real-time operat When calling a standard libc or C++ sleep function, such as ``usleep`` defined in ``unistd.h``, the task will only block and yield the core if the sleep time is longer than :ref:`one FreeRTOS tick period `. If the time is shorter, the thread will busy-wait instead of yielding to another RTOS task. +.. note:: + + The POSIX ``errno`` is provided by newlib in ESP-IDF. Thus the configuration ``configUSE_POSIX_ERRNO`` is not used and should stay disabled. + By default, all POSIX Threads have the same RTOS priority, but it is possible to change this by calling a :ref:`custom API `. Standard Features @@ -169,6 +177,53 @@ Thread-Specific Data There are other options for thread local storage in ESP-IDF, including options with higher performance. See :doc:`/api-guides/thread-local-storage`. +.. _posix_message_queues: + +Message Queues +^^^^^^^^^^^^^^ + +The message queue implementation is based on the `FreeRTOS-Plus-POSIX `_ project. Message queues are not made available in any filesystem on ESP-IDF. Message priorities are not supported. +The following API functions of the POSIX message queue specification are implemented: + +* `mq_open() `_ + + - The ``name`` argument has, besides the POSIX specification, the following additional restrictions: + - It has to begin with a leading slash. + - It has to be no more than 255 + 2 characters long (including the leading slash, excluding the terminating null byte). However, memory for ``name`` is dynamically allocated internally, so the shorter it is, the fewer memory it will consume. + - The ``mode`` argument is not implemented and is ignored. + - Supported ``oflags``: ``O_RDWR``, ``O_CREAT``, ``O_EXCL``, and ``O_NONBLOCK`` + +* `mq_close() `_ +* `mq_unlink() `_ +* `mq_receive() `_ + + - Since message priorities are not supported, ``msg_prio`` is unused. + +* `mq_timedreceive() `_ + + - Since message priorities are not supported, ``msg_prio`` is unused. + +* `mq_send() `_ + + - Since message priorities are not supported, ``msg_prio`` has no effect. + +* `mq_timedsend() `_ + + - Since message priorities are not supported, ``msg_prio`` has no effect. + +* `mq_getattr() `_ + +`mq_notify() `_ and `mq_setattr() `_ are not implemented. + +Building +........ + +To use the POSIX message queue API, please add ``rt`` as a requirement in your component's ``CMakeLists.txt`` + +.. note:: + + If you have used `FreeRTOS-Plus-POSIX `_ in another FreeRTOS project before, please note that the include paths in IDF are POSIX-like. Hence, applications include ``mqueue.h`` directly instead of using the subdirectory include ``FreeRTOS_POSIX/mqueue.h``. + Not Implemented --------------- @@ -176,8 +231,10 @@ The ``pthread.h`` header is a standard header and includes additional APIs and f * ``pthread_cancel()`` returns ``ENOSYS`` if called. * ``pthread_condattr_init()`` returns ``ENOSYS`` if called. +* `mq_notify() `_ returns ``ENOSYS`` if called. +* `mq_setattr() `_ returns ``ENOSYS`` if called. -Other POSIX Threads functions (not listed here) are not implemented and will produce either a compiler or a linker error if referenced from an ESP-IDF application. If you identify a useful API that you would like to see implemented in ESP-IDF, please open a `feature request on GitHub `_ with the details. +Other POSIX Threads functions (not listed here) are not implemented and will produce either a compiler or a linker error if referenced from an ESP-IDF application. .. _esp-pthread: diff --git a/examples/system/.build-test-rules.yml b/examples/system/.build-test-rules.yml index 25030dccbf..4c7dfc8f39 100644 --- a/examples/system/.build-test-rules.yml +++ b/examples/system/.build-test-rules.yml @@ -236,6 +236,13 @@ examples/system/pthread: depends_components: - pthread +examples/system/rt_mqueue: + disable_test: + - if: IDF_TARGET != "esp32" and (NIGHTLY_RUN != "1" or IDF_TARGET == "linux") + reason: no target specific functionality, testing on a single target is sufficient + depends_components: + - rt + examples/system/select: disable: - if: IDF_TARGET != "esp32c3" and (NIGHTLY_RUN != "1" or IDF_TARGET == "linux") diff --git a/examples/system/rt_mqueue/CMakeLists.txt b/examples/system/rt_mqueue/CMakeLists.txt new file mode 100644 index 0000000000..f02d51120a --- /dev/null +++ b/examples/system/rt_mqueue/CMakeLists.txt @@ -0,0 +1,8 @@ +# For more information about build system see +# https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html +# The following five lines of boilerplate have to be in your project's +# CMakeLists in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.16) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(posix_mqueue) diff --git a/examples/system/rt_mqueue/README.md b/examples/system/rt_mqueue/README.md new file mode 100644 index 0000000000..f5400e9674 --- /dev/null +++ b/examples/system/rt_mqueue/README.md @@ -0,0 +1,37 @@ +| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C5 | ESP32-C6 | ESP32-H2 | ESP32-P4 | ESP32-S2 | ESP32-S3 | +| ----------------- | ----- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | + +# POSIX Message Queue Example + +A simple example using a POSIX message queue. Two tasks are reading from and writing to a POSIX message queue. + +## How to use example + +### Hardware Required + +This example should be able to run on any supported Espressif SoC development board. + +### Build and Flash + +Enter `idf.py -p PORT flash monitor` to build, flash and monitor the project. + +(To exit the serial monitor, type ``Ctrl-]``.) + +See the [Getting Started Guide](https://docs.espressif.com/projects/esp-idf/en/latest/get-started/index.html) for full steps to configure and use ESP-IDF to build projects. + + +## Example Output + +If you see the following infinite console output, your example should be running correctly: + +``` +sending: 0 +received: 0 +sending: 1 +received: 1 +sending: 2 +received: 2 +sending: 3 +received: 3 +... +``` diff --git a/examples/system/rt_mqueue/main/CMakeLists.txt b/examples/system/rt_mqueue/main/CMakeLists.txt new file mode 100644 index 0000000000..fb0d84fa9c --- /dev/null +++ b/examples/system/rt_mqueue/main/CMakeLists.txt @@ -0,0 +1,3 @@ +idf_component_register(SRCS "posix_mqueue_example_main.c" + INCLUDE_DIRS "." + PRIV_REQUIRES rt) diff --git a/examples/system/rt_mqueue/main/posix_mqueue_example_main.c b/examples/system/rt_mqueue/main/posix_mqueue_example_main.c new file mode 100644 index 0000000000..235fbc48e4 --- /dev/null +++ b/examples/system/rt_mqueue/main/posix_mqueue_example_main.c @@ -0,0 +1,101 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static int s_counter = 0; + +const unsigned int MSG_PRIO = 0; + +static void *sender_function(void * arg) +{ + mqd_t *write_descr = (mqd_t*) arg; + + while (true) { + printf("sending: %d\n", s_counter); + int result = mq_send(*write_descr, (const char*) &s_counter, sizeof(s_counter), MSG_PRIO); + if (result != 0) { + perror("Sending failed"); + abort(); + } + + s_counter++; + sleep(1); + } + + return NULL; +} + +static void *receiver_function(void * arg) +{ + mqd_t *read_descr = (mqd_t*) arg; + + while (true) { + int msg; + int result = mq_receive(*read_descr, (char*) &msg, sizeof(msg), NULL); + if (result == -1) { + perror("Sending failed"); + abort(); + } + + printf("received: %d\n", msg); + } + + return NULL; +} + +void app_main(void) +{ + mqd_t write_descr; + mqd_t read_descr; + pthread_t sender; + pthread_t receiver; + + struct mq_attr configuration = { + .mq_flags = 0, // ignored by mq_open + .mq_maxmsg = 10, + .mq_msgsize = sizeof(int), + .mq_curmsgs = 0 // ignored by mq_open + }; + + write_descr = mq_open("/my_queue", O_CREAT | O_WRONLY, S_IRUSR | S_IWUSR , &configuration); + if (write_descr == (mqd_t) -1) { + perror("Creating message queue failed"); + abort(); + } + + read_descr = mq_open("/my_queue", O_RDONLY); + if (read_descr == (mqd_t) -1) { + perror("Opening message queue for reading failed"); + abort(); + } + + int result; + result = pthread_create(&sender, NULL, sender_function, &write_descr); + if (result != 0) { + printf("Creating sender thread failed: %s", strerror(errno)); + abort(); + } + + result = pthread_create(&receiver, NULL, receiver_function, &read_descr); + if (result != 0) { + printf("Creating receiver thread failed: %s", strerror(errno)); + abort(); + } + + while (true) { + sleep(1000); + } +} diff --git a/examples/system/rt_mqueue/pytest_rt_mqueue.py b/examples/system/rt_mqueue/pytest_rt_mqueue.py new file mode 100644 index 0000000000..893553de85 --- /dev/null +++ b/examples/system/rt_mqueue/pytest_rt_mqueue.py @@ -0,0 +1,13 @@ +# SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: CC0-1.0 +import pytest +from pytest_embedded import Dut + + +@pytest.mark.supported_targets +@pytest.mark.generic +def test_rt_mqueue_example(dut: Dut) -> None: + dut.expect_exact('sending: 0') + dut.expect_exact('received: 0') + dut.expect_exact('sending: 1') + dut.expect_exact('received: 1') diff --git a/tools/ci/astyle-rules.yml b/tools/ci/astyle-rules.yml index e7b761b8b9..e74f913eb5 100644 --- a/tools/ci/astyle-rules.yml +++ b/tools/ci/astyle-rules.yml @@ -152,6 +152,8 @@ components_not_formatted_permanent: - "/components/app_trace/sys_view/Config/" - "/components/app_trace/sys_view/Sample/" - "/components/app_trace/sys_view/SEGGER/" + # FreeRTOS-Plux-POSIX files (upstream source code) + - "/components/rt/" # SoC header files (generated) - "/components/soc/*/include/soc/" # Example resource files (generated) diff --git a/tools/ci/check_copyright_config.yaml b/tools/ci/check_copyright_config.yaml index 9850f85dd9..aadfcc5d55 100644 --- a/tools/ci/check_copyright_config.yaml +++ b/tools/ci/check_copyright_config.yaml @@ -111,6 +111,22 @@ linux_component: - Apache-2.0 - BSD-4-Clause-UC +rt_component: + include: + - 'components/rt/**' + allowed_licenses: + - Apache-2.0 + - MIT #FreeRTOS-Plux-POSIX sources and port files + +rt_component_tests: + include: + - 'components/rt/test_apps/**' + allowed_licenses: + - Apache-2.0 + - Unlicense + - CC0-1.0 + license_for_new_files: Unlicense OR CC0-1.0 + systemview: include: - 'components/app_trace/sys_view'