diff --git a/CMakeLists.txt b/CMakeLists.txt index 546606c1..9fbe6982 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -82,6 +82,24 @@ target_sources(freertos-threadx ) target_link_libraries(freertos-threadx PUBLIC threadx) +# Define the POSIX compatibility layer. It supplies its own errno.h, fcntl.h, +# pthread.h, sched.h, signal.h and time.h, so it cannot be combined with the +# hosted simulation ports: those build against a full C library that already +# provides those headers, and the layer's pthread.h even shares the _PTHREAD_H +# include guard with glibc's. Skip it there rather than offering a target that +# could never build. +# +# It is also single-core by construction: px_int.h declares +# _tx_thread_current_ptr as a plain pointer, which is a per-core array in an SMP +# build, and the layer has no notion of which core it is running on. +if(THREADX_ARCH MATCHES "^(linux|win32|win64)$") + message(STATUS "POSIX compatibility layer not available for ${THREADX_ARCH}") +elseif(THREADX_SMP) + message(STATUS "POSIX compatibility layer not available for SMP builds") +else() + add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/utility/rtos_compatibility_layers/posix) +endif() + # If the user provided an override, copy it to the custom directory if (NOT TX_USER_FILE) message(STATUS "Using default tx_user.h file") diff --git a/scripts/build_posix.sh b/scripts/build_posix.sh new file mode 100755 index 00000000..ac81c8ff --- /dev/null +++ b/scripts/build_posix.sh @@ -0,0 +1,17 @@ +#!/bin/bash +############################################################################## +# Copyright (c) 2026 Eclipse ThreadX contributors +# +# This program and the accompanying materials are made available under the +# terms of the MIT License which is available at +# https://opensource.org/licenses/MIT. +# +# AI Disclosure: This file was largely AI-generated by Claude Code (Opus 5). +# The AI-generated portions may be considered public domain (CC0-1.0) +# and not subject to the project's licence. The human contributor has +# reviewed and verified that the code is correct. +# +# SPDX-License-Identifier: MIT and CC0-1.0 +############################################################################## + +$(dirname `realpath $0`)/../test/posix/cmake/run.sh riscv32 build all diff --git a/scripts/test_posix.sh b/scripts/test_posix.sh new file mode 100755 index 00000000..ce851cf4 --- /dev/null +++ b/scripts/test_posix.sh @@ -0,0 +1,17 @@ +#!/bin/bash +############################################################################## +# Copyright (c) 2026 Eclipse ThreadX contributors +# +# This program and the accompanying materials are made available under the +# terms of the MIT License which is available at +# https://opensource.org/licenses/MIT. +# +# AI Disclosure: This file was largely AI-generated by Claude Code (Opus 5). +# The AI-generated portions may be considered public domain (CC0-1.0) +# and not subject to the project's licence. The human contributor has +# reviewed and verified that the code is correct. +# +# SPDX-License-Identifier: MIT and CC0-1.0 +############################################################################## + +$(dirname `realpath $0`)/../test/posix/cmake/run.sh riscv32 test all diff --git a/test/posix/cmake/CMakeLists.txt b/test/posix/cmake/CMakeLists.txt new file mode 100644 index 00000000..830e69ba --- /dev/null +++ b/test/posix/cmake/CMakeLists.txt @@ -0,0 +1,83 @@ +cmake_minimum_required(VERSION 3.13 FATAL_ERROR) +cmake_policy(SET CMP0054 NEW) +cmake_policy(SET CMP0057 NEW) + +# Regression tests for the POSIX compatibility layer. +# +# The layer cannot run on the hosted simulation ports: it supplies its own +# pthread.h, errno.h, time.h and friends, which collide with the C library's, +# and the linux port emulates threads with the very pthread calls the layer +# exports. These tests therefore run on emulated targets, reusing the RISC-V +# BSP and QEMU setup that the ThreadX RISC-V regression suite already uses. + +project(threadx_posix_test LANGUAGES C ASM) + +set(BUILD_CONFIGURATIONS default_build disable_notify_callbacks_build + stack_checking_build trace_build) +set(CMAKE_CONFIGURATION_TYPES + ${BUILD_CONFIGURATIONS} + CACHE STRING "list of supported configuration types" FORCE) +set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + ${CMAKE_CONFIGURATION_TYPES}) +list(GET CMAKE_CONFIGURATION_TYPES 0 BUILD_TYPE) +if((NOT CMAKE_BUILD_TYPE) OR (NOT ("${CMAKE_BUILD_TYPE}" IN_LIST + CMAKE_CONFIGURATION_TYPES))) + set(CMAKE_BUILD_TYPE + "${BUILD_TYPE}" + CACHE STRING "Build Type of the project" FORCE) +endif() + +message(STATUS "Build type: ${CMAKE_BUILD_TYPE}") +message(STATUS "Using toolchain file: ${CMAKE_TOOLCHAIN_FILE}.") +message(STATUS "THREADX_ARCH: ${THREADX_ARCH}") + +set(default_build -DTX_QUEUE_MESSAGE_MAX_SIZE=32) +set(disable_notify_callbacks_build -DTX_QUEUE_MESSAGE_MAX_SIZE=32 -DTX_DISABLE_NOTIFY_CALLBACKS) +set(stack_checking_build -DTX_QUEUE_MESSAGE_MAX_SIZE=32 -DTX_ENABLE_STACK_CHECKING) +set(trace_build -DTX_QUEUE_MESSAGE_MAX_SIZE=32 -DTX_ENABLE_EVENT_TRACE) + +# These tests are 32-bit only for now, and the reason is a gap in the layer +# rather than a choice about coverage. +# +# The layer passes a message by putting the address of a private buffer into the +# queue. ULONG is 32 bits on every ThreadX port, including the 64-bit ones, so +# on a 64-bit target that address only fits if TX_64_BIT is defined: the layer +# then splits it across two message words and sizes the message accordingly. +# Without it, px_mq_send.c truncates the pointer to 32 bits and px_mq_receive.c +# casts the truncated value back, which the compiler reports only as a warning. +# +# Defining TX_64_BIT is not a way out either: tx_api.h then uses the extension +# pointer macros, which need tx_thread_extension_ptr in the thread control +# block, and no 64-bit port outside ports_smp and ports/linux declares it. +# Closing that gap belongs with the layer, not with its tests. +if(THREADX_ARCH STREQUAL "risc-v64") + message(FATAL_ERROR + "The POSIX layer cannot run on a 64-bit port yet: it needs TX_64_BIT to " + "carry a 64-bit message pointer, and TX_64_BIT needs a port that declares " + "tx_thread_extension_ptr. Use the riscv32 toolchain file for these tests.") +endif() + +add_compile_options( + -std=c99 + -O0 + -g3 + -fdiagnostics-color + -Werror + -ffunction-sections + -fdata-sections + -DTX_REGRESSION_TEST + -DTEST_STACK_SIZE_PRINTF=4096 + -DEXTERNAL_EXIT + ${${CMAKE_BUILD_TYPE}} +) + +enable_testing() + +# ThreadX itself, plus the posix-threadx target the top-level file defines. +add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/../../.. threadx) + +# Reuse the RISC-V BSP from the ThreadX regression suite rather than keeping a +# second copy of the board support in this tree. +add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/../../tx/cmake/riscv/bsp riscv_bsp) + +add_subdirectory(regression) diff --git a/test/posix/cmake/regression/CMakeLists.txt b/test/posix/cmake/regression/CMakeLists.txt new file mode 100644 index 00000000..c8320b05 --- /dev/null +++ b/test/posix/cmake/regression/CMakeLists.txt @@ -0,0 +1,64 @@ +cmake_minimum_required(VERSION 3.13 FATAL_ERROR) +cmake_policy(SET CMP0057 NEW) + +project(posix_regression_test LANGUAGES C ASM) + +set(SOURCE_DIR ${CMAKE_CURRENT_LIST_DIR}/../../regression) +set(TX_TEST_DIR ${CMAKE_CURRENT_LIST_DIR}/../../../tx/regression) +set(BSP_DIR ${CMAKE_CURRENT_LIST_DIR}/../../../tx/cmake/riscv/bsp) + +if(THREADX_ARCH STREQUAL "risc-v64") + set(QEMU_SYSTEM "qemu-system-riscv64") + set(XLEN_TAG "rv64") +else() + set(QEMU_SYSTEM "qemu-system-riscv32") + set(XLEN_TAG "rv32") +endif() + +set(posix_test_cases + ${SOURCE_DIR}/posix_mq_basic_test.c + ${SOURCE_DIR}/posix_mq_send_abort_test.c + ${SOURCE_DIR}/posix_pthread_basic_test.c +) + +set(WEAK_DEFAULTS_SOURCE ${CMAKE_CURRENT_LIST_DIR}/../../../shared/regression/testcontrol_weak_defaults.c) + +# Weak defaults must be an OBJECT library so all objects are always linked into +# each test executable. A STATIC library would have the linker skip them, since +# weak definitions do not satisfy undefined strong references while scanning an +# archive. +add_library(posix_test_weak_defaults OBJECT ${WEAK_DEFAULTS_SOURCE}) +target_compile_definitions(posix_test_weak_defaults PRIVATE + CTEST BATCH_TEST EXTERNAL_EXIT TEST_STACK_SIZE_PRINTF=4096) +target_compile_options(posix_test_weak_defaults PRIVATE -include stdlib.h) +target_link_libraries(posix_test_weak_defaults PRIVATE azrtos::threadx) + +# testcontrol.c supplies main(), tx_application_define() and +# test_control_return(), and is shared with the ThreadX regression suite. +add_library(posix_test_utility ${TX_TEST_DIR}/testcontrol.c) +target_link_libraries(posix_test_utility PUBLIC riscv_bsp azrtos::threadx) +target_compile_definitions(posix_test_utility PUBLIC + CTEST BATCH_TEST EXTERNAL_EXIT TEST_STACK_SIZE_PRINTF=4096) +target_compile_options(posix_test_utility PUBLIC -include stdlib.h) + +function(register_posix_test test_name) + target_link_options(${test_name} PRIVATE + -T ${BSP_DIR}/link.lds -nostartfiles + -Wl,--gc-sections) + add_test( + NAME ${CMAKE_BUILD_TYPE}::${XLEN_TAG}::${test_name} + COMMAND ${QEMU_SYSTEM} -nographic -smp 1 -bios none -m 128M + -machine virt -kernel $ + ) + set_tests_properties(${CMAKE_BUILD_TYPE}::${XLEN_TAG}::${test_name} + PROPERTIES TIMEOUT 120) +endfunction() + +foreach(test_case ${posix_test_cases}) + get_filename_component(test_name ${test_case} NAME_WE) + add_executable(${test_name} ${test_case} $) + target_link_libraries(${test_name} PRIVATE + -Wl,--whole-archive riscv_bsp -Wl,--no-whole-archive + posix_test_utility posix-threadx) + register_posix_test(${test_name}) +endforeach() diff --git a/test/posix/cmake/run.sh b/test/posix/cmake/run.sh new file mode 100755 index 00000000..aa65f7a7 --- /dev/null +++ b/test/posix/cmake/run.sh @@ -0,0 +1,166 @@ +#!/bin/bash +############################################################################## +# Copyright (c) 2026 Eclipse ThreadX contributors +# +# This program and the accompanying materials are made available under the +# terms of the MIT License which is available at +# https://opensource.org/licenses/MIT. +# +# AI Disclosure: This file was largely AI-generated by Claude Code (Opus 5). +# The AI-generated portions may be considered public domain (CC0-1.0) +# and not subject to the project's licence. The human contributor has +# reviewed and verified that the code is correct. +# +# SPDX-License-Identifier: MIT and CC0-1.0 +############################################################################## + + +set -e + +# POSIX compatibility layer regression test build/test runner. +# Usage: run.sh [all| ...] +# arch: riscv32 +# +# Only riscv32 is available: the layer cannot carry a 64-bit message pointer +# without TX_64_BIT, and TX_64_BIT needs a port that declares +# tx_thread_extension_ptr. See the comment in ../CMakeLists.txt. + +function help() { + echo "Usage: $0 [all| ...]" + echo "" + echo " arch: riscv32" + echo " command: build | test" + echo "" + echo "Available build configurations:" + for build in ${build_configurations[*]}; do + echo " $build" + done + exit 1 +} + +function validate() { + for build in ${build_configurations[*]}; do + if [ "$1" == "$build" ]; then + return + fi + done + help +} + +function generate() { + local arch=$1 + local build=$2 + local toolchain_file + local build_dir + + toolchain_file="${REPO_ROOT}/cmake/riscv32_gnu.cmake" + + build_dir="build/${arch}_${build}" + cmake -B"$build_dir" -GNinja \ + -DCMAKE_TOOLCHAIN_FILE="$toolchain_file" \ + -DCMAKE_BUILD_TYPE="$build" \ + . +} + +function build() { + local arch=$1 + local build=$2 + cmake --build "build/${arch}_${build}" +} + +function run_test() { + local arch=$1 + local build=$2 + local parallel_jobs=$3 + + pushd "build/${arch}_${build}" + [ -z "${CTEST_PARALLEL_LEVEL}" ] && parallel="-j${parallel_jobs}" + if [ -z "${CTEST_REPEAT_FAIL}" ]; then + repeat_fail=2 + else + repeat_fail=${CTEST_REPEAT_FAIL} + fi + ctest $parallel --timeout 1000 \ + -O "${arch}_${build}.txt" \ + -T test --no-compress-output \ + --test-output-size-passed 4194304 \ + --test-output-size-failed 4194304 \ + --output-on-failure \ + --repeat until-pass:${repeat_fail} \ + --output-junit "${arch}_${build}.xml" + popd +} + +# Determine repo root and script directory +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +REPO_ROOT="$(cd "${SCRIPT_DIR}/../../.." && pwd)" +cd "$SCRIPT_DIR" + +# Parse build configurations from CMakeLists.txt +result=$(sed -n "/(BUILD_CONFIGURATIONS/,/)/p" CMakeLists.txt | sed ':label;N;s/\n/ /;b label' | grep -Pzo "[a-zA-Z0-9_]*build[a-zA-Z0-9_]*\s*" | tr -d '\0') +IFS=' ' +read -ra build_configurations <<< "$result" + +if [ $# -lt 2 ]; then + help +fi + +arch=$1 +shift + +if [ "$arch" != "riscv32" ]; then + echo "Error: arch must be 'riscv32'" + help +fi + +command=$1 +shift + +if [ "$#" == "0" ]; then + builds=${build_configurations[0]} +elif [ "$*" == "all" ]; then + builds=${build_configurations[@]} +else + for item in $*; do + validate "$item" + done + builds=$* +fi + +if [ "$command" == "build" ]; then + for item in $builds; do + generate "$arch" "$item" + echo "" + done + + for item in $builds; do + echo "Building ${arch} ${item}" + build "$arch" "$item" + echo "" + done +elif [ "$command" == "test" ]; then + cores=$(nproc) + if [ -z "${CTEST_PARALLEL_LEVEL}" ]; then + build_counts=$(echo $builds | wc -w) + parallel_jobs=$(($cores / $build_counts)) + parallel_jobs=$(($parallel_jobs + 2)) + pids="" + for item in $builds; do + echo "Testing ${arch} ${item}" + run_test "$arch" "$item" "$parallel_jobs" & + pids+=" $!" + done + exit_code=0 + for p in $pids; do + wait $p || exit_code=$? + done + exit $exit_code + else + for item in $builds; do + echo "Testing ${arch} ${item}" + run_test "$arch" "$item" "$parallel_jobs" + done + fi +else + help +fi diff --git a/test/posix/regression/posix_mq_basic_test.c b/test/posix/regression/posix_mq_basic_test.c new file mode 100644 index 00000000..2ecb1ac8 --- /dev/null +++ b/test/posix/regression/posix_mq_basic_test.c @@ -0,0 +1,159 @@ +/***************************************************************************/ +/* Copyright (c) 2026 Eclipse ThreadX contributors */ +/* */ +/* This program and the accompanying materials are made available under */ +/* the terms of the MIT License which is available at */ +/* https://opensource.org/licenses/MIT. */ +/* */ +/* AI Disclosure: This file was largely AI-generated by Claude Code */ +/* (Opus 5). The AI-generated portions may be considered public domain */ +/* (CC0-1.0) and not subject to the project's licence. The human */ +/* contributor has reviewed and verified that the code is correct. */ +/* */ +/* SPDX-License-Identifier: MIT and CC0-1.0 */ +/***************************************************************************/ + +/* This test is designed to test basic POSIX message queue operation: open a + queue, send a message through it, receive the message back and confirm both + the contents and the priority survived the round trip. */ + +#include +#include "tx_api.h" +#include "pthread.h" + +#define TEST_MESSAGE_SIZE 40 +#define TEST_QUEUE_DEPTH 10 +#define TEST_PRIORITY 3 + +/* Memory the POSIX layer carves its own objects out of. */ +static ULONG posix_region[192 * 1024 / sizeof(ULONG)]; + +static pthread_t worker; +static pthread_attr_t worker_attr; +static mqd_t queue_des; +static struct mq_attr queue_attr; + +extern void test_control_return(UINT status); + +static VOID *worker_entry(VOID *input); + + +/* Define what the initial system looks like. */ + +#ifdef CTEST +void test_application_define(void *first_unused_memory) +#else +void posix_mq_basic_application_define(void *first_unused_memory) +#endif +{ + +struct sched_param param; +VOID *storage_ptr; + + (void) first_unused_memory; + + /* Bring the POSIX layer up. It returns the first address beyond its own + structures, which is where pthread stacks are taken from. */ + storage_ptr = posix_initialize(posix_region); + + queue_attr.mq_maxmsg = TEST_QUEUE_DEPTH; + queue_attr.mq_msgsize = TEST_MESSAGE_SIZE; + + queue_des = mq_open("mq_basic", O_CREAT | O_RDWR, 0, &queue_attr); + if (queue_des == (mqd_t) ERROR) + { + + printf("Running POSIX Message Queue Basic Test............................. ERROR #1\n"); + test_control_return(1); + } + + pthread_attr_init(&worker_attr); + param.sched_priority = 10; + pthread_attr_setschedparam(&worker_attr, ¶m); + pthread_attr_setstackaddr(&worker_attr, storage_ptr); + + if (pthread_create(&worker, &worker_attr, worker_entry, NULL) != OK) + { + + printf("Running POSIX Message Queue Basic Test............................. ERROR #2\n"); + test_control_return(1); + } +} + + +static VOID *worker_entry(VOID *input) +{ + +CHAR sent[TEST_MESSAGE_SIZE]; +CHAR received[TEST_MESSAGE_SIZE]; +ULONG received_priority; +ssize_t received_length; +INT i; + + (void) input; + + printf("Running POSIX Message Queue Basic Test............................. "); + + /* Build a recognisable payload rather than a constant one, so a message + that is dropped or truncated cannot pass by accident. */ + for (i = 0; i < TEST_MESSAGE_SIZE; i++) + { + + sent[i] = (CHAR) (i + 1); + received[i] = (CHAR) 0; + } + + if (mq_send(queue_des, sent, TEST_MESSAGE_SIZE, TEST_PRIORITY) != OK) + { + + printf("ERROR #3\n"); + test_control_return(1); + } + + received_priority = 0; + received_length = mq_receive(queue_des, received, TEST_MESSAGE_SIZE, &received_priority); + + if (received_length != (ssize_t) TEST_MESSAGE_SIZE) + { + + printf("ERROR #4\n"); + test_control_return(1); + } + + if (received_priority != (ULONG) TEST_PRIORITY) + { + + printf("ERROR #5\n"); + test_control_return(1); + } + + for (i = 0; i < TEST_MESSAGE_SIZE; i++) + { + + if (received[i] != sent[i]) + { + + printf("ERROR #6\n"); + test_control_return(1); + } + } + + if (mq_close(queue_des) != OK) + { + + printf("ERROR #7\n"); + test_control_return(1); + } + + if (mq_unlink("mq_basic") != OK) + { + + printf("ERROR #8\n"); + test_control_return(1); + } + + printf("SUCCESS!\n"); + test_control_return(0); + + return (NULL); +} diff --git a/test/posix/regression/posix_mq_send_abort_test.c b/test/posix/regression/posix_mq_send_abort_test.c new file mode 100644 index 00000000..a4e7e7e6 --- /dev/null +++ b/test/posix/regression/posix_mq_send_abort_test.c @@ -0,0 +1,273 @@ +/***************************************************************************/ +/* Copyright (c) 2026 Eclipse ThreadX contributors */ +/* */ +/* This program and the accompanying materials are made available under */ +/* the terms of the MIT License which is available at */ +/* https://opensource.org/licenses/MIT. */ +/* */ +/* AI Disclosure: This file was largely AI-generated by Claude Code */ +/* (Opus 5). The AI-generated portions may be considered public domain */ +/* (CC0-1.0) and not subject to the project's licence. The human */ +/* contributor has reviewed and verified that the code is correct. */ +/* */ +/* SPDX-License-Identifier: MIT and CC0-1.0 */ +/***************************************************************************/ + +/* This test covers the message buffer mq_send() used to leak when the send + failed, reported as issue #568. + + mq_send() allocates a private buffer from the queue's own byte pool, copies + the caller's message into it and passes the address through the queue. The + receiver owns the buffer from then on and releases it. When the send itself + fails the message never reaches a receiver, so mq_send() has to release the + buffer before returning or it is lost. + + The failure is provoked the way an application would meet it: the queue is + filled, a sender blocks on it, and its wait is aborted. The queue survives, + so any buffer that is not released stays lost. Aborting repeatedly and + watching the queue's byte pool separates the two outcomes: with the buffer + released the pool returns to its original size, and without it the pool + shrinks by a message every time. */ + +#include +#include "tx_api.h" +#include "pthread.h" + +/* The queue's pool is sized (mq_maxmsg + 1) * (mq_msgsize + 11), with a floor + of 100 bytes, so it holds barely more than a full queue. Keep the messages + small and the queue shallow: that floor then leaves room for a few leaked + buffers to accumulate and be measured, instead of exhausting the pool on the + first one and hanging the sender inside posix_internal_error(). */ +#define TEST_MESSAGE_SIZE 8 +#define TEST_QUEUE_DEPTH 1 +#define TEST_ABORT_COUNT 2 + +static ULONG posix_region[192 * 1024 / sizeof(ULONG)]; + +static pthread_t sender; +static pthread_attr_t sender_attr; +static pthread_t controller; +static pthread_attr_t controller_attr; + +static mqd_t queue_des; +static struct mq_attr queue_attr; + +/* Written by the sender, read by the controller. */ +static TX_THREAD * volatile sender_thread_ptr; +static volatile INT send_failures; +static volatile INT stop_sending; + +extern void test_control_return(UINT status); + +static VOID *sender_entry(VOID *input); +static VOID *controller_entry(VOID *input); + + +/* Report how many bytes the queue's message pool has free. */ +static ULONG pool_available(void) +{ + +ULONG available; + + available = 0; + tx_byte_pool_info_get(&(queue_des -> f_data -> vq_message_area), + TX_NULL, &available, TX_NULL, TX_NULL, TX_NULL, TX_NULL); + + return (available); +} + + +/* Define what the initial system looks like. */ + +#ifdef CTEST +void test_application_define(void *first_unused_memory) +#else +void posix_mq_send_abort_application_define(void *first_unused_memory) +#endif +{ + +struct sched_param param; +VOID *storage_ptr; + + (void) first_unused_memory; + + storage_ptr = posix_initialize(posix_region); + + queue_attr.mq_maxmsg = TEST_QUEUE_DEPTH; + queue_attr.mq_msgsize = TEST_MESSAGE_SIZE; + + queue_des = mq_open("mq_abort", O_CREAT | O_RDWR, 0, &queue_attr); + if (queue_des == (mqd_t) ERROR) + { + + printf("Running POSIX Message Queue Send Abort Test....................... ERROR #1\n"); + test_control_return(1); + } + + /* The controller drives the test and runs above the sender, so it keeps + control while the sender is being unblocked. */ + pthread_attr_init(&controller_attr); + param.sched_priority = 15; + pthread_attr_setschedparam(&controller_attr, ¶m); + pthread_attr_setstackaddr(&controller_attr, storage_ptr); + storage_ptr = (ULONG *) storage_ptr + (4096 / sizeof(ULONG)); + + pthread_attr_init(&sender_attr); + param.sched_priority = 10; + pthread_attr_setschedparam(&sender_attr, ¶m); + pthread_attr_setstackaddr(&sender_attr, storage_ptr); + + if (pthread_create(&controller, &controller_attr, controller_entry, NULL) != OK) + { + + printf("Running POSIX Message Queue Send Abort Test....................... ERROR #2\n"); + test_control_return(1); + } + + if (pthread_create(&sender, &sender_attr, sender_entry, NULL) != OK) + { + + printf("Running POSIX Message Queue Send Abort Test....................... ERROR #3\n"); + test_control_return(1); + } +} + + +/* Sends until told to stop. Every send blocks, because the controller keeps + the queue full, and every one is released by an aborted wait. */ +static VOID *sender_entry(VOID *input) +{ + +CHAR message[TEST_MESSAGE_SIZE]; +INT i; + + (void) input; + + for (i = 0; i < TEST_MESSAGE_SIZE; i++) + { + + message[i] = (CHAR) i; + } + + /* Publish our own thread pointer so the controller can abort our wait + without casting a pthread ID back to a ThreadX thread. */ + sender_thread_ptr = tx_thread_identify(); + + while (stop_sending == 0) + { + + if (mq_send(queue_des, message, TEST_MESSAGE_SIZE, 1) != OK) + { + + send_failures++; + } + } + + return (NULL); +} + + +static VOID *controller_entry(VOID *input) +{ + +CHAR message[TEST_MESSAGE_SIZE]; +ULONG baseline; +ULONG after; +ULONG lost; +INT i; + + (void) input; + + printf("Running POSIX Message Queue Send Abort Test....................... "); + + for (i = 0; i < TEST_MESSAGE_SIZE; i++) + { + + message[i] = (CHAR) i; + } + + /* Fill the queue so that any further send has to wait. */ + if (mq_send(queue_des, message, TEST_MESSAGE_SIZE, 1) != OK) + { + + printf("ERROR #4\n"); + test_control_return(1); + } + + /* Let the sender reach its first blocking send, then take the baseline. + The sender is holding one allocation at this point, which is the state + every later measurement is compared against. */ + tx_thread_sleep(2); + + if (sender_thread_ptr == TX_NULL) + { + + printf("ERROR #5\n"); + test_control_return(1); + } + + baseline = pool_available(); + + for (i = 0; i < TEST_ABORT_COUNT; i++) + { + + /* Release the sender from its wait. Its send fails, and the buffer it + allocated has to come back to the pool. */ + if (tx_thread_wait_abort(sender_thread_ptr) != TX_SUCCESS) + { + + printf("ERROR #6\n"); + test_control_return(1); + } + + /* Give the sender time to return from mq_send() and block again. */ + tx_thread_sleep(2); + } + + if (send_failures != TEST_ABORT_COUNT) + { + + printf("ERROR #7\n"); + test_control_return(1); + } + + /* The sender is blocked again holding exactly one allocation, the same as + at the baseline, so the pool should read the same. */ + after = pool_available(); + + if (after > baseline) + { + + printf("ERROR #8\n"); + test_control_return(1); + } + + lost = baseline - after; + + /* Anything approaching a message means buffers are not coming back. A + leak would cost TEST_ABORT_COUNT messages, so this separates the two + cases comfortably without depending on byte pool overhead. */ + if (lost >= (ULONG) TEST_MESSAGE_SIZE) + { + + printf("ERROR #9\n"); + test_control_return(1); + } + + /* Shut the sender down and confirm the queue still works. */ + stop_sending = 1; + tx_thread_wait_abort(sender_thread_ptr); + tx_thread_sleep(2); + + if (mq_receive(queue_des, message, TEST_MESSAGE_SIZE, TX_NULL) != (ssize_t) TEST_MESSAGE_SIZE) + { + + printf("ERROR #10\n"); + test_control_return(1); + } + + printf("SUCCESS!\n"); + test_control_return(0); + + return (NULL); +} diff --git a/test/posix/regression/posix_pthread_basic_test.c b/test/posix/regression/posix_pthread_basic_test.c new file mode 100644 index 00000000..afb8d4f3 --- /dev/null +++ b/test/posix/regression/posix_pthread_basic_test.c @@ -0,0 +1,192 @@ +/***************************************************************************/ +/* Copyright (c) 2026 Eclipse ThreadX contributors */ +/* */ +/* This program and the accompanying materials are made available under */ +/* the terms of the MIT License which is available at */ +/* https://opensource.org/licenses/MIT. */ +/* */ +/* AI Disclosure: This file was largely AI-generated by Claude Code */ +/* (Opus 5). The AI-generated portions may be considered public domain */ +/* (CC0-1.0) and not subject to the project's licence. The human */ +/* contributor has reviewed and verified that the code is correct. */ +/* */ +/* SPDX-License-Identifier: MIT and CC0-1.0 */ +/***************************************************************************/ + +/* This test covers basic pthread and synchronisation operation: a pthread runs + and reports its own identity, a mutex serialises access to shared state, a + semaphore signals between two pthreads, and a join collects the worker's exit + value. */ + +#include +#include "tx_api.h" +/* The layer ships no semaphore.h; sem_t and the sem_* calls arrive through + pthread.h, which pulls in tx_posix.h. */ +#include "pthread.h" + +#define TEST_STACK_BYTES 4096 + +static ULONG posix_region[192 * 1024 / sizeof(ULONG)]; + +static pthread_t worker; +static pthread_attr_t worker_attr; +static pthread_t driver; +static pthread_attr_t driver_attr; + +static pthread_mutex_t guard; +static sem_t handoff; + +static volatile INT shared_counter; +static pthread_t worker_self_id; + +static INT worker_result = 77; + +extern void test_control_return(UINT status); + +static VOID *worker_entry(VOID *input); +static VOID *driver_entry(VOID *input); + + +/* Define what the initial system looks like. */ + +#ifdef CTEST +void test_application_define(void *first_unused_memory) +#else +void posix_pthread_basic_application_define(void *first_unused_memory) +#endif +{ + +struct sched_param param; +VOID *storage_ptr; + + (void) first_unused_memory; + + storage_ptr = posix_initialize(posix_region); + + if (pthread_mutex_init(&guard, NULL) != OK) + { + + printf("Running POSIX Pthread Basic Test.................................. ERROR #1\n"); + test_control_return(1); + } + + if (sem_init(&handoff, 0, 0) != OK) + { + + printf("Running POSIX Pthread Basic Test.................................. ERROR #2\n"); + test_control_return(1); + } + + /* The driver runs below the worker so that the worker reaches its first + blocking point before the driver inspects anything. */ + pthread_attr_init(&worker_attr); + param.sched_priority = 15; + pthread_attr_setschedparam(&worker_attr, ¶m); + pthread_attr_setstackaddr(&worker_attr, storage_ptr); + storage_ptr = (ULONG *) storage_ptr + (TEST_STACK_BYTES / sizeof(ULONG)); + + pthread_attr_init(&driver_attr); + param.sched_priority = 10; + pthread_attr_setschedparam(&driver_attr, ¶m); + pthread_attr_setstackaddr(&driver_attr, storage_ptr); + + if (pthread_create(&worker, &worker_attr, worker_entry, NULL) != OK) + { + + printf("Running POSIX Pthread Basic Test.................................. ERROR #3\n"); + test_control_return(1); + } + + if (pthread_create(&driver, &driver_attr, driver_entry, NULL) != OK) + { + + printf("Running POSIX Pthread Basic Test.................................. ERROR #4\n"); + test_control_return(1); + } +} + + +static VOID *worker_entry(VOID *input) +{ + +INT i; + + (void) input; + + /* Record what this pthread believes its own ID to be, for the driver to + compare against the ID pthread_create() handed back. */ + worker_self_id = pthread_self(); + + for (i = 0; i < 100; i++) + { + + pthread_mutex_lock(&guard); + shared_counter++; + pthread_mutex_unlock(&guard); + } + + /* Hand control to the driver. */ + sem_post(&handoff); + + return ((VOID *) &worker_result); +} + + +static VOID *driver_entry(VOID *input) +{ + +VOID *exit_value; + + (void) input; + + printf("Running POSIX Pthread Basic Test.................................. "); + + /* Wait for the worker to finish its counting. */ + if (sem_wait(&handoff) != OK) + { + + printf("ERROR #5\n"); + test_control_return(1); + } + + if (shared_counter != 100) + { + + printf("ERROR #6\n"); + test_control_return(1); + } + + if (worker_self_id != worker) + { + + printf("ERROR #7\n"); + test_control_return(1); + } + + exit_value = NULL; + if (pthread_join(worker, &exit_value) != OK) + { + + printf("ERROR #8\n"); + test_control_return(1); + } + + if (exit_value != (VOID *) &worker_result) + { + + printf("ERROR #9\n"); + test_control_return(1); + } + + if (pthread_mutex_destroy(&guard) != OK) + { + + printf("ERROR #10\n"); + test_control_return(1); + } + + printf("SUCCESS!\n"); + test_control_return(0); + + return (NULL); +} diff --git a/utility/rtos_compatibility_layers/posix/CMakeLists.txt b/utility/rtos_compatibility_layers/posix/CMakeLists.txt new file mode 100644 index 00000000..6ddcd6a8 --- /dev/null +++ b/utility/rtos_compatibility_layers/posix/CMakeLists.txt @@ -0,0 +1,129 @@ +# POSIX compatibility layer for ThreadX. +# +# This target is EXCLUDE_FROM_ALL: it is not part of the default build and is +# only produced when something asks for it, either by name or by linking it. +# +# The include directory is deliberately PUBLIC. The layer ships replacements for +# errno.h, fcntl.h, pthread.h, sched.h, signal.h and time.h, and application code +# has to see those rather than a toolchain's own copies. That is also why the +# layer cannot be used on the hosted simulation ports, where the C library +# already supplies them; the top-level CMakeLists.txt skips it there. + +add_library(posix-threadx EXCLUDE_FROM_ALL) + +target_include_directories(posix-threadx + PUBLIC + ${CMAKE_CURRENT_LIST_DIR} +) + +target_sources(posix-threadx + PRIVATE + ${CMAKE_CURRENT_LIST_DIR}/px_abs_time_to_rel_ticks.c + ${CMAKE_CURRENT_LIST_DIR}/px_clock_getres.c + ${CMAKE_CURRENT_LIST_DIR}/px_clock_gettime.c + ${CMAKE_CURRENT_LIST_DIR}/px_clock_settime.c + ${CMAKE_CURRENT_LIST_DIR}/px_cond_broadcast.c + ${CMAKE_CURRENT_LIST_DIR}/px_cond_destroy.c + ${CMAKE_CURRENT_LIST_DIR}/px_cond_init.c + ${CMAKE_CURRENT_LIST_DIR}/px_cond_signal.c + ${CMAKE_CURRENT_LIST_DIR}/px_cond_timedwait.c + ${CMAKE_CURRENT_LIST_DIR}/px_cond_wait.c + ${CMAKE_CURRENT_LIST_DIR}/px_error.c + ${CMAKE_CURRENT_LIST_DIR}/px_in_thread_context.c + ${CMAKE_CURRENT_LIST_DIR}/px_internal_signal_dispatch.c + ${CMAKE_CURRENT_LIST_DIR}/px_memory_allocate.c + ${CMAKE_CURRENT_LIST_DIR}/px_memory_release.c + ${CMAKE_CURRENT_LIST_DIR}/px_mq_arrange_msg.c + ${CMAKE_CURRENT_LIST_DIR}/px_mq_attr_init.c + ${CMAKE_CURRENT_LIST_DIR}/px_mq_close.c + ${CMAKE_CURRENT_LIST_DIR}/px_mq_create.c + ${CMAKE_CURRENT_LIST_DIR}/px_mq_find_queue.c + ${CMAKE_CURRENT_LIST_DIR}/px_mq_get_new_queue.c + ${CMAKE_CURRENT_LIST_DIR}/px_mq_get_queue_desc.c + ${CMAKE_CURRENT_LIST_DIR}/px_mq_open.c + ${CMAKE_CURRENT_LIST_DIR}/px_mq_priority_search.c + ${CMAKE_CURRENT_LIST_DIR}/px_mq_putback_queue.c + ${CMAKE_CURRENT_LIST_DIR}/px_mq_queue_delete.c + ${CMAKE_CURRENT_LIST_DIR}/px_mq_queue_init.c + ${CMAKE_CURRENT_LIST_DIR}/px_mq_receive.c + ${CMAKE_CURRENT_LIST_DIR}/px_mq_reset_queue.c + ${CMAKE_CURRENT_LIST_DIR}/px_mq_send.c + ${CMAKE_CURRENT_LIST_DIR}/px_mq_unlink.c + ${CMAKE_CURRENT_LIST_DIR}/px_mx_attr_destroy.c + ${CMAKE_CURRENT_LIST_DIR}/px_mx_attr_getprotocol.c + ${CMAKE_CURRENT_LIST_DIR}/px_mx_attr_getpshared.c + ${CMAKE_CURRENT_LIST_DIR}/px_mx_attr_gettype.c + ${CMAKE_CURRENT_LIST_DIR}/px_mx_attr_initi.c + ${CMAKE_CURRENT_LIST_DIR}/px_mx_attr_setprotocol.c + ${CMAKE_CURRENT_LIST_DIR}/px_mx_attr_setpshared.c + ${CMAKE_CURRENT_LIST_DIR}/px_mx_attr_settype.c + ${CMAKE_CURRENT_LIST_DIR}/px_mx_destroy.c + ${CMAKE_CURRENT_LIST_DIR}/px_mx_init.c + ${CMAKE_CURRENT_LIST_DIR}/px_mx_lock.c + ${CMAKE_CURRENT_LIST_DIR}/px_mx_set_default_mutexattr.c + ${CMAKE_CURRENT_LIST_DIR}/px_mx_timedlock.c + ${CMAKE_CURRENT_LIST_DIR}/px_mx_trylock.c + ${CMAKE_CURRENT_LIST_DIR}/px_mx_unlock.c + ${CMAKE_CURRENT_LIST_DIR}/px_nanosleep.c + ${CMAKE_CURRENT_LIST_DIR}/px_pth_attr_destroy.c + ${CMAKE_CURRENT_LIST_DIR}/px_pth_attr_getdetachstate.c + ${CMAKE_CURRENT_LIST_DIR}/px_pth_attr_getinheritsched.c + ${CMAKE_CURRENT_LIST_DIR}/px_pth_attr_getschedparam.c + ${CMAKE_CURRENT_LIST_DIR}/px_pth_attr_getschedpolicy.c + ${CMAKE_CURRENT_LIST_DIR}/px_pth_attr_getstack.c + ${CMAKE_CURRENT_LIST_DIR}/px_pth_attr_getstackaddr.c + ${CMAKE_CURRENT_LIST_DIR}/px_pth_attr_getstacksize.c + ${CMAKE_CURRENT_LIST_DIR}/px_pth_attr_init.c + ${CMAKE_CURRENT_LIST_DIR}/px_pth_attr_setdetachstate.c + ${CMAKE_CURRENT_LIST_DIR}/px_pth_attr_setinheritsched.c + ${CMAKE_CURRENT_LIST_DIR}/px_pth_attr_setschedparam.c + ${CMAKE_CURRENT_LIST_DIR}/px_pth_attr_setschedpolicyl.c + ${CMAKE_CURRENT_LIST_DIR}/px_pth_attr_setstack.c + ${CMAKE_CURRENT_LIST_DIR}/px_pth_attr_setstackaddr.c + ${CMAKE_CURRENT_LIST_DIR}/px_pth_attr_setstacksize.c + ${CMAKE_CURRENT_LIST_DIR}/px_pth_cancel.c + ${CMAKE_CURRENT_LIST_DIR}/px_pth_create.c + ${CMAKE_CURRENT_LIST_DIR}/px_pth_detach.c + ${CMAKE_CURRENT_LIST_DIR}/px_pth_equal.c + ${CMAKE_CURRENT_LIST_DIR}/px_pth_exit.c + ${CMAKE_CURRENT_LIST_DIR}/px_pth_getcanceltype.c + ${CMAKE_CURRENT_LIST_DIR}/px_pth_getschedparam.c + ${CMAKE_CURRENT_LIST_DIR}/px_pth_init.c + ${CMAKE_CURRENT_LIST_DIR}/px_pth_join.c + ${CMAKE_CURRENT_LIST_DIR}/px_pth_kill.c + ${CMAKE_CURRENT_LIST_DIR}/px_pth_once.c + ${CMAKE_CURRENT_LIST_DIR}/px_pth_self.c + ${CMAKE_CURRENT_LIST_DIR}/px_pth_set_default_pthread_attr.c + ${CMAKE_CURRENT_LIST_DIR}/px_pth_setcancelstate.c + ${CMAKE_CURRENT_LIST_DIR}/px_pth_setcanceltype.c + ${CMAKE_CURRENT_LIST_DIR}/px_pth_setschedparam.c + ${CMAKE_CURRENT_LIST_DIR}/px_pth_sigmask.c + ${CMAKE_CURRENT_LIST_DIR}/px_pth_testcancel.c + ${CMAKE_CURRENT_LIST_DIR}/px_pth_yield.c + ${CMAKE_CURRENT_LIST_DIR}/px_px_initialize.c + ${CMAKE_CURRENT_LIST_DIR}/px_sched_get_prio.c + ${CMAKE_CURRENT_LIST_DIR}/px_sched_yield.c + ${CMAKE_CURRENT_LIST_DIR}/px_sem_close.c + ${CMAKE_CURRENT_LIST_DIR}/px_sem_destroy.c + ${CMAKE_CURRENT_LIST_DIR}/px_sem_find_sem.c + ${CMAKE_CURRENT_LIST_DIR}/px_sem_get_new_sem.c + ${CMAKE_CURRENT_LIST_DIR}/px_sem_getvalue.c + ${CMAKE_CURRENT_LIST_DIR}/px_sem_init.c + ${CMAKE_CURRENT_LIST_DIR}/px_sem_open.c + ${CMAKE_CURRENT_LIST_DIR}/px_sem_post.c + ${CMAKE_CURRENT_LIST_DIR}/px_sem_reset.c + ${CMAKE_CURRENT_LIST_DIR}/px_sem_set_sem_name.c + ${CMAKE_CURRENT_LIST_DIR}/px_sem_trywait.c + ${CMAKE_CURRENT_LIST_DIR}/px_sem_unlink.c + ${CMAKE_CURRENT_LIST_DIR}/px_sem_wait.c + ${CMAKE_CURRENT_LIST_DIR}/px_sig_addset.c + ${CMAKE_CURRENT_LIST_DIR}/px_sig_delset.c + ${CMAKE_CURRENT_LIST_DIR}/px_sig_emptyset.c + ${CMAKE_CURRENT_LIST_DIR}/px_sig_fillset.c + ${CMAKE_CURRENT_LIST_DIR}/px_sig_signal.c + ${CMAKE_CURRENT_LIST_DIR}/px_sig_wait.c + ${CMAKE_CURRENT_LIST_DIR}/px_sleep.c + ${CMAKE_CURRENT_LIST_DIR}/px_system_manager.c +) + +target_link_libraries(posix-threadx PUBLIC threadx) diff --git a/utility/rtos_compatibility_layers/posix/tx_posix.h b/utility/rtos_compatibility_layers/posix/tx_posix.h index ce533dd8..3f235cb9 100644 --- a/utility/rtos_compatibility_layers/posix/tx_posix.h +++ b/utility/rtos_compatibility_layers/posix/tx_posix.h @@ -278,9 +278,22 @@ typedef struct pthread_attr_obj } pthread_attr_t; -typedef INT ssize_t ; /* this should be pulled in from sys\types.h */ +/* ssize_t and mode_t belong to . A C library that has already + declared them wins: it knows the target's real widths, and on an LP64 target + ssize_t is 64 bits while INT is not. newlib guards each type with its own + _*_DECLARED macro, so honour those and only define what is missing. A port + whose library uses different guards can predefine them to the same effect. */ +#ifndef _SSIZE_T_DECLARED +typedef INT ssize_t ; +#define _SSIZE_T_DECLARED +#endif + typedef ALIGN_TYPE pthread_t; + +#ifndef _MODE_T_DECLARED typedef ULONG mode_t; +#define _MODE_T_DECLARED +#endif