diff --git a/dmod-config.h.in b/dmod-config.h.in index a4c5037c..104f6b2f 100644 --- a/dmod-config.h.in +++ b/dmod-config.h.in @@ -13,6 +13,7 @@ #define DMOD_USE_DIRENT @DMOD_USE_DIRENT@ #define DMOD_USE_ASSERT @DMOD_USE_ASSERT@ #define DMOD_USE_PTHREAD @DMOD_USE_PTHREAD@ +#define DMOD_USE_NANOSLEEP @DMOD_USE_NANOSLEEP@ #define DMOD_USE_MMAN @DMOD_USE_MMAN@ #define DMOD_USE_ALIGNED_ALLOC @DMOD_USE_ALIGNED_ALLOC@ #define DMOD_USE_ALIGNED_MALLOC_MOCK @DMOD_USE_ALIGNED_MALLOC_MOCK@ diff --git a/dmod-defaults.cmake b/dmod-defaults.cmake index 2955a90e..b2746ad0 100644 --- a/dmod-defaults.cmake +++ b/dmod-defaults.cmake @@ -36,6 +36,9 @@ endif() if (NOT DEFINED DMOD_USE_PTHREAD) set(DMOD_USE_PTHREAD ON) endif() +if (NOT DEFINED DMOD_USE_NANOSLEEP) + set(DMOD_USE_NANOSLEEP ON) +endif() if (NOT DEFINED DMOD_USE_MMAN) set(DMOD_USE_MMAN ON) endif() diff --git a/inc/dmod_sal.h b/inc/dmod_sal.h index def81386..1d13220f 100644 --- a/inc/dmod_sal.h +++ b/inc/dmod_sal.h @@ -83,6 +83,11 @@ DMOD_BUILTIN_API(Dmod, 1.0, void*, _AlignedMallocEx, ( size_t Size, size_t Al DMOD_BUILTIN_API(Dmod, 1.0, void, _FreeModule, ( const char* ModuleName ) ); DMOD_BUILTIN_API(Dmod, 1.0, size_t, _ReadMemory, ( uintptr_t Address, void* Buffer, size_t Size ) ); DMOD_BUILTIN_API(Dmod, 1.0, size_t, _WriteMemory, ( uintptr_t Address, const void* Buffer, size_t Size ) ); +DMOD_BUILTIN_API(Dmod, 1.0, bool, _IsRam, ( const void* Address ) ); +DMOD_BUILTIN_API(Dmod, 1.0, bool, _IsRom, ( const void* Address ) ); +DMOD_BUILTIN_API(Dmod, 1.0, bool, _IsDma, ( const void* Address ) ); +DMOD_BUILTIN_API(Dmod, 1.0, bool, _IsExt, ( const void* Address ) ); +DMOD_BUILTIN_API(Dmod, 1.0, bool, _IsAddressValid, ( const void* Address ) ); //! @} /** @@ -266,6 +271,8 @@ DMOD_BUILTIN_API(Dmod, 1.0, void*, _Mutex_New, ( bool Recursive ) ); DMOD_BUILTIN_API(Dmod, 1.0, int , _Mutex_Lock, ( void* Mutex ) ); DMOD_BUILTIN_API(Dmod, 1.0, int , _Mutex_Unlock, ( void* Mutex ) ); DMOD_BUILTIN_API(Dmod, 1.0, void , _Mutex_Delete, ( void* Mutex ) ); +DMOD_BUILTIN_API(Dmod, 1.0, bool , _DelayUs, ( uint64_t Microseconds ) ); +DMOD_BUILTIN_API(Dmod, 1.0, bool , _SleepMs, ( uint64_t Milliseconds ) ); //! @} diff --git a/src/system/if/dmod_if_rawmem.c b/src/system/if/dmod_if_rawmem.c index 8f1e5427..713c4cc7 100644 --- a/src/system/if/dmod_if_rawmem.c +++ b/src/system/if/dmod_if_rawmem.c @@ -207,3 +207,166 @@ DMOD_INPUT_WEAK_API_DECLARATION(Dmod, 1.0, size_t, _WriteMemory, ( uintptr_t Add memcpy((void*)Address, Buffer, Size); return Size; } + +/** + * @brief Helper function to parse address from environment variable + * + * @param EnvName Environment variable name + * + * @return Parsed address or 0 if not set or invalid + */ +static uintptr_t Dmod_GetEnvAddress(const char* EnvName) +{ + const char* value = Dmod_GetEnv(EnvName); + if (value == NULL || value[0] == '\0') + { + return 0; + } + + // Parse hex or decimal address + uintptr_t addr = 0; + if (value[0] == '0' && value[1] != '\0' && (value[1] == 'x' || value[1] == 'X')) + { + // Hex format + for (int i = 2; value[i] != '\0'; i++) + { + char c = value[i]; + addr <<= 4; + if (c >= '0' && c <= '9') + addr |= (c - '0'); + else if (c >= 'a' && c <= 'f') + addr |= (c - 'a' + 10); + else if (c >= 'A' && c <= 'F') + addr |= (c - 'A' + 10); + else + return 0; // Invalid character + } + } + else + { + // Decimal format + for (int i = 0; value[i] != '\0'; i++) + { + char c = value[i]; + if (c >= '0' && c <= '9') + { + addr = addr * 10 + (c - '0'); + } + else + { + return 0; // Invalid character + } + } + } + + return addr; +} + +/** + * @brief Helper function to check if address is in a memory region + * + * @param Address Address to check + * @param pStart Pointer to static variable for region start address + * @param pEnd Pointer to static variable for region end address + * @param envStart Name of environment variable for start address + * @param envEnd Name of environment variable for end address + * + * @return true if address is in the region, false otherwise + */ +static bool Dmod_IsAddressInRegion(const void* Address, uintptr_t* pStart, uintptr_t* pEnd, + const char* envStart, const char* envEnd) +{ + if (Address == NULL) + { + return false; + } + + // Read environment variables on first call (cache the values) + *pStart = *pStart != 0 ? *pStart : Dmod_GetEnvAddress(envStart); + *pEnd = *pEnd != 0 ? *pEnd : Dmod_GetEnvAddress(envEnd); + + // If not configured, just check if address is not NULL + if (*pEnd == 0) + { + return true; // Address is not NULL, consider it potentially valid + } + + uintptr_t addr = (uintptr_t)Address; + return (addr >= *pStart && addr < *pEnd); +} + +/** + * @brief Check if address is in RAM + * + * @param Address Address to check + * + * @return true if address is in RAM, false otherwise + */ +DMOD_INPUT_WEAK_API_DECLARATION(Dmod, 1.0, bool, _IsRam, ( const void* Address )) +{ + static uintptr_t ramStart = 0; + static uintptr_t ramEnd = 0; + return Dmod_IsAddressInRegion(Address, &ramStart, &ramEnd, "DMOD_RAM_START", "DMOD_RAM_END"); +} + +/** + * @brief Check if address is in ROM + * + * @param Address Address to check + * + * @return true if address is in ROM, false otherwise + */ +DMOD_INPUT_WEAK_API_DECLARATION(Dmod, 1.0, bool, _IsRom, ( const void* Address )) +{ + static uintptr_t romStart = 0; + static uintptr_t romEnd = 0; + return Dmod_IsAddressInRegion(Address, &romStart, &romEnd, "DMOD_ROM_START", "DMOD_ROM_END"); +} + +/** + * @brief Check if address is in DMA region + * + * @param Address Address to check + * + * @return true if address is in DMA region, false otherwise + */ +DMOD_INPUT_WEAK_API_DECLARATION(Dmod, 1.0, bool, _IsDma, ( const void* Address )) +{ + static uintptr_t dmaStart = 0; + static uintptr_t dmaEnd = 0; + return Dmod_IsAddressInRegion(Address, &dmaStart, &dmaEnd, "DMOD_DMA_START", "DMOD_DMA_END"); +} + +/** + * @brief Check if address is in External memory + * + * @param Address Address to check + * + * @return true if address is in External memory, false otherwise + */ +DMOD_INPUT_WEAK_API_DECLARATION(Dmod, 1.0, bool, _IsExt, ( const void* Address )) +{ + static uintptr_t extStart = 0; + static uintptr_t extEnd = 0; + return Dmod_IsAddressInRegion(Address, &extStart, &extEnd, "DMOD_EXT_START", "DMOD_EXT_END"); +} + +/** + * @brief Check if address is valid (in any known memory region) + * + * @param Address Address to check + * + * @return true if address is valid (RAM || ROM || DMA || EXT), false otherwise + */ +DMOD_INPUT_WEAK_API_DECLARATION(Dmod, 1.0, bool, _IsAddressValid, ( const void* Address )) +{ + if (Address == NULL) + { + return false; + } + + return Dmod_IsRam(Address) || + Dmod_IsRom(Address) || + Dmod_IsDma(Address) || + Dmod_IsExt(Address); +} diff --git a/src/system/if/dmod_if_rtos.c b/src/system/if/dmod_if_rtos.c index 8f41a2ef..89d4e28a 100644 --- a/src/system/if/dmod_if_rtos.c +++ b/src/system/if/dmod_if_rtos.c @@ -32,6 +32,10 @@ # define __USE_UNIX98 # include #endif +#if DMOD_USE_NANOSLEEP +# include +# include +#endif //============================================================================== // FUNCTIONS DECLARATIONS @@ -173,3 +177,79 @@ DMOD_INPUT_WEAK_API_DECLARATION(Dmod, 1.0, void, _Mutex_Delete, ( void* Mutex )) } #endif } + +/** + * @brief Delay execution for a specified number of microseconds + * + * @param Microseconds Number of microseconds to delay + * + * @return true if delay was successful, false otherwise + */ +DMOD_INPUT_WEAK_API_DECLARATION(Dmod, 1.0, bool, _DelayUs, ( uint64_t Microseconds )) +{ + #if DMOD_USE_NANOSLEEP + if (Microseconds == 0) + { + return true; + } + + // Use nanosleep for precision + struct timespec ts; + ts.tv_sec = Microseconds / 1000000; + ts.tv_nsec = (Microseconds % 1000000) * 1000; + + while (nanosleep(&ts, &ts) == -1) + { + // Continue if interrupted by signal + if (errno != EINTR) + { + return false; + } + } + + return true; + #else + // Platform not supported + (void)Microseconds; + DMOD_LOG_WARN("Dmod_DelayUs interface not implemented for this platform\n"); + return false; + #endif +} + +/** + * @brief Sleep for a specified number of milliseconds + * + * @param Milliseconds Number of milliseconds to sleep + * + * @return true if sleep was successful, false otherwise + */ +DMOD_INPUT_WEAK_API_DECLARATION(Dmod, 1.0, bool, _SleepMs, ( uint64_t Milliseconds )) +{ + #if DMOD_USE_NANOSLEEP + if (Milliseconds == 0) + { + return true; + } + + // Use nanosleep for better precision + struct timespec ts; + ts.tv_sec = Milliseconds / 1000; + ts.tv_nsec = (Milliseconds % 1000) * 1000000; + + while (nanosleep(&ts, &ts) == -1) + { + // Continue if interrupted by signal + if (errno != EINTR) + { + return false; + } + } + + return true; + #else + // Platform not supported + (void)Milliseconds; + DMOD_LOG_WARN("Dmod_SleepMs interface not implemented for this platform\n"); + return false; + #endif +} diff --git a/tests/system/if/CMakeLists.txt b/tests/system/if/CMakeLists.txt index bafed9f3..908776b6 100644 --- a/tests/system/if/CMakeLists.txt +++ b/tests/system/if/CMakeLists.txt @@ -5,5 +5,7 @@ set(TEST_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/tests_dmod_dir.cpp ${CMAKE_CURRENT_SOURCE_DIR}/tests_dmod_snprintf.cpp ${CMAKE_CURRENT_SOURCE_DIR}/tests_dmod_env.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/tests_dmod_memregion.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/tests_dmod_delay.cpp PARENT_SCOPE ) diff --git a/tests/system/if/tests_dmod_delay.cpp b/tests/system/if/tests_dmod_delay.cpp new file mode 100644 index 00000000..067579c9 --- /dev/null +++ b/tests/system/if/tests_dmod_delay.cpp @@ -0,0 +1,164 @@ +#define DMOD_PRIVATE +#include +#include +#include "dmod_sal.h" + +// =============================================================== +// Tests for Delay and Sleep Functions +// =============================================================== + +class DmodDelayTest : public ::testing::Test +{ +protected: + void SetUp() override + { + } + + void TearDown() override + { + } + + // Helper function to measure elapsed time in microseconds + template + uint64_t measureTime(Func func) + { + auto start = std::chrono::high_resolution_clock::now(); + func(); + auto end = std::chrono::high_resolution_clock::now(); + return std::chrono::duration_cast(end - start).count(); + } +}; + +/** + * @brief Test for Dmod_DelayUs with zero delay + * + * The test checks if the function handles zero delay correctly. + */ +TEST_F(DmodDelayTest, DelayUsZero) +{ + bool result = Dmod_DelayUs(0); + ASSERT_TRUE(result); +} + +/** + * @brief Test for Dmod_DelayUs with small delay + * + * The test checks if the function can delay for a small amount of time. + */ +TEST_F(DmodDelayTest, DelayUsSmall) +{ + uint64_t delayUs = 1000; // 1 millisecond + + uint64_t elapsed = measureTime([delayUs]() { + bool result = Dmod_DelayUs(delayUs); + ASSERT_TRUE(result); + }); + + // Allow 50% tolerance due to system scheduling and timer precision + ASSERT_GE(elapsed, delayUs / 2); + // Upper bound should be reasonable (less than 10x expected) + ASSERT_LE(elapsed, delayUs * 10); +} + +/** + * @brief Test for Dmod_DelayUs with larger delay + * + * The test checks if the function can delay for a larger amount of time. + */ +TEST_F(DmodDelayTest, DelayUsLarge) +{ + uint64_t delayUs = 10000; // 10 milliseconds + + uint64_t elapsed = measureTime([delayUs]() { + bool result = Dmod_DelayUs(delayUs); + ASSERT_TRUE(result); + }); + + // Allow 30% tolerance for larger delays + ASSERT_GE(elapsed, delayUs * 0.7); + ASSERT_LE(elapsed, delayUs * 1.5); +} + +/** + * @brief Test for Dmod_SleepMs with zero sleep + * + * The test checks if the function handles zero sleep correctly. + */ +TEST_F(DmodDelayTest, SleepMsZero) +{ + bool result = Dmod_SleepMs(0); + ASSERT_TRUE(result); +} + +/** + * @brief Test for Dmod_SleepMs with small sleep + * + * The test checks if the function can sleep for a small amount of time. + */ +TEST_F(DmodDelayTest, SleepMsSmall) +{ + uint64_t sleepMs = 10; // 10 milliseconds + + uint64_t elapsed = measureTime([sleepMs]() { + bool result = Dmod_SleepMs(sleepMs); + ASSERT_TRUE(result); + }); + + // Convert to microseconds for comparison + uint64_t expectedUs = sleepMs * 1000; + + // Allow 50% tolerance due to system scheduling + ASSERT_GE(elapsed, expectedUs / 2); + ASSERT_LE(elapsed, expectedUs * 3); +} + +/** + * @brief Test for Dmod_SleepMs with larger sleep + * + * The test checks if the function can sleep for a larger amount of time. + */ +TEST_F(DmodDelayTest, SleepMsLarge) +{ + uint64_t sleepMs = 50; // 50 milliseconds + + uint64_t elapsed = measureTime([sleepMs]() { + bool result = Dmod_SleepMs(sleepMs); + ASSERT_TRUE(result); + }); + + // Convert to microseconds for comparison + uint64_t expectedUs = sleepMs * 1000; + + // Allow 30% tolerance for larger sleeps + ASSERT_GE(elapsed, expectedUs * 0.7); + ASSERT_LE(elapsed, expectedUs * 1.5); +} + +/** + * @brief Test that DelayUs and SleepMs produce consistent results + * + * The test checks if both functions produce similar delays. + */ +TEST_F(DmodDelayTest, DelayVsSleepConsistency) +{ + uint64_t delayMs = 20; + + uint64_t delayElapsed = measureTime([delayMs]() { + Dmod_DelayUs(delayMs * 1000); + }); + + uint64_t sleepElapsed = measureTime([delayMs]() { + Dmod_SleepMs(delayMs); + }); + + // Both should be reasonably close (within 3x of each other) + // Ensure neither value is zero to avoid division by zero + ASSERT_GT(delayElapsed, 0u); + ASSERT_GT(sleepElapsed, 0u); + + uint64_t ratio = (delayElapsed > sleepElapsed) ? + (delayElapsed / sleepElapsed) : + (sleepElapsed / delayElapsed); + + ASSERT_LE(ratio, 3); // Allow up to 3x difference +} diff --git a/tests/system/if/tests_dmod_memregion.cpp b/tests/system/if/tests_dmod_memregion.cpp new file mode 100644 index 00000000..423918ec --- /dev/null +++ b/tests/system/if/tests_dmod_memregion.cpp @@ -0,0 +1,135 @@ +#define DMOD_PRIVATE +#include +#include +#include "dmod_sal.h" + +// =============================================================== +// Tests for Memory Region Checking Functions +// =============================================================== + +class DmodMemoryRegionTest : public ::testing::Test +{ +protected: + void SetUp() override + { + } + + void TearDown() override + { + } +}; + +/** + * @brief Test for Dmod_IsAddressValid with NULL + * + * The test checks if the function correctly identifies NULL as invalid. + */ +TEST_F(DmodMemoryRegionTest, IsAddressValidNull) +{ + ASSERT_FALSE(Dmod_IsAddressValid(NULL)); +} + +/** + * @brief Test for Dmod_IsAddressValid with valid address + * + * The test checks if the function can identify valid addresses. + * On non-embedded systems, this may return false as memory regions + * are not well-defined. + */ +TEST_F(DmodMemoryRegionTest, IsAddressValidStackAddress) +{ + int stackVar = 42; + void* address = &stackVar; + + // On x86_64/PC systems, memory region checking may not be available + // so we just ensure the function returns without crashing + bool result = Dmod_IsAddressValid(address); + (void)result; // Result depends on platform +} + +/** + * @brief Test for Dmod_IsRam + * + * The test checks if the function works without crashing. + * Actual RAM detection depends on platform configuration. + */ +TEST_F(DmodMemoryRegionTest, IsRam) +{ + int stackVar = 42; + void* address = &stackVar; + + // Function should not crash regardless of result + bool result = Dmod_IsRam(address); + (void)result; // Result depends on platform +} + +/** + * @brief Test for Dmod_IsRom + * + * The test checks if the function works without crashing. + * Actual ROM detection depends on platform configuration. + */ +TEST_F(DmodMemoryRegionTest, IsRom) +{ + // Use function pointer which typically points to code segment + void* address = (void*)&Dmod_IsRom; + + // Function should not crash regardless of result + bool result = Dmod_IsRom(address); + (void)result; // Result depends on platform +} + +/** + * @brief Test for Dmod_IsDma + * + * The test checks if the function works without crashing. + * Actual DMA detection depends on platform configuration. + */ +TEST_F(DmodMemoryRegionTest, IsDma) +{ + int stackVar = 42; + void* address = &stackVar; + + // Function should not crash regardless of result + bool result = Dmod_IsDma(address); + (void)result; // Result depends on platform +} + +/** + * @brief Test for Dmod_IsExt + * + * The test checks if the function works without crashing. + * Actual external memory detection depends on platform configuration. + */ +TEST_F(DmodMemoryRegionTest, IsExt) +{ + int stackVar = 42; + void* address = &stackVar; + + // Function should not crash regardless of result + bool result = Dmod_IsExt(address); + (void)result; // Result depends on platform +} + +/** + * @brief Test IsAddressValid logic consistency + * + * IsAddressValid should return true if any of IsRam, IsRom, IsDma, or IsExt returns true. + */ +TEST_F(DmodMemoryRegionTest, IsAddressValidConsistency) +{ + int stackVar = 42; + void* address = &stackVar; + + bool isValid = Dmod_IsAddressValid(address); + bool isRam = Dmod_IsRam(address); + bool isRom = Dmod_IsRom(address); + bool isDma = Dmod_IsDma(address); + bool isExt = Dmod_IsExt(address); + + // If any individual region check is true, IsAddressValid should be true + if (isRam || isRom || isDma || isExt) + { + ASSERT_TRUE(isValid); + } +}