diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..a55b8b3 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,41 @@ +name: CI + +on: + push: + branches: [ master, develop ] + pull_request: + branches: [ master, develop, feature/**, copilot/** ] + +jobs: + build-and-test: + name: Build and Test + runs-on: ubuntu-latest + permissions: + contents: read + container: + image: chocotechnologies/dmod:1.0.4 + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Build dm_sw_ring project + run: | + mkdir -p build + cd build + cmake .. -DDMOD_MODE=DMOD_MODULE + cmake --build . + + - name: Verify module files + run: | + echo "Checking for module files..." + ls -lh build/dmf/ + test -f build/dmf/dm_sw_ring.dmf + test -f build/dmf/dm_sw_ring_version.txt + test -f build/dmf/test_dm_sw_ring.dmf + echo "Module files present" + + - name: Run tests with dmod_loader + run: | + export DMOD_DMF_DIR=$(pwd)/build/dmf + dmod_loader build/dmf/test_dm_sw_ring.dmf diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..332e177 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,249 @@ +name: Release + +on: + release: + types: [created] + +jobs: + discover-architectures: + name: Discover Architectures + runs-on: ubuntu-latest + permissions: + contents: read + container: + image: chocotechnologies/dmod:1.0.4 + outputs: + architectures: ${{ steps.list-archs.outputs.architectures }} + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Fetch dmod to discover architectures + run: | + mkdir -p build_discovery + cd build_discovery + cmake .. -DDMOD_MODE=DMOD_MODULE + + - name: List available architectures + id: list-archs + run: | + DMOD_SRC_DIR=$(find build_discovery -path "*/_deps/dmod-src" -type d | head -1) + + # create JSON array + ARCHS=$(find ${DMOD_SRC_DIR}/configs/arch -name "tools-cfg.cmake" | \ + sed "s|${DMOD_SRC_DIR}/configs/arch/||g" | \ + sed 's|/tools-cfg.cmake||g' | \ + jq -R -s -c 'split("\n") | map(select(length > 0))') + + echo "Found architectures: $ARCHS" + echo "architectures=$ARCHS" >> $GITHUB_OUTPUT + + build-release: + name: Build Release for ${{ matrix.arch_name }} + needs: discover-architectures + runs-on: ubuntu-latest + permissions: + contents: write + strategy: + matrix: + arch_name: ${{ fromJson(needs.discover-architectures.outputs.architectures) }} + + container: + image: chocotechnologies/dmod:1.0.4 + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Extract version from tag + id: get_version + run: | + # Extract version from tag (e.g., v1.2 -> 1.2) + VERSION="${{ github.event.release.tag_name }}" + VERSION="${VERSION#v}" # Remove 'v' prefix if present + echo "version=$VERSION" >> $GITHUB_OUTPUT + echo "Extracted version: $VERSION" + + - name: Build dm_sw_ring for ${{ matrix.arch_name }} + run: | + set -e + ARCH_DIR_NAME=$(echo "${{ matrix.arch_name }}" | sed 's|/|-|') + echo "ARCH_DIR_NAME=$ARCH_DIR_NAME" >> $GITHUB_ENV + echo "ARTIFACT_NAME=release-$ARCH_DIR_NAME" >> $GITHUB_ENV + mkdir -p build_$ARCH_DIR_NAME + cd build_$ARCH_DIR_NAME + + cmake .. -DDMOD_TOOLS_NAME=arch/${{ matrix.arch_name }} -DDMOD_MODULE_VERSION="${{ steps.get_version.outputs.version }}" + cmake --build . + + echo "Build completed for ${{ matrix.arch_name }}" + ls -la packages/ + + - name: Add release notes and create release archive + run: | + set -e + BUILD_DIR="build_$ARCH_DIR_NAME" + TAG="${{ github.event.release.tag_name }}" + + # Add release notes to dm_sw_ring package and create release archive + echo "${{ github.event.release.body }}" > "$BUILD_DIR/packages/dm_sw_ring/RELEASE_NOTES.txt" + cd "$BUILD_DIR/packages/dm_sw_ring" + zip -r "${OLDPWD}/dm_sw_ring-${TAG}-${ARCH_DIR_NAME}.zip" . + cd - + + echo "Created archive:" + ls -lh dm_sw_ring-*.zip + + - name: Upload artifact + uses: actions/upload-artifact@v4.4.3 + with: + name: ${{ env.ARTIFACT_NAME }} + path: "dm_sw_ring-*.zip" + retention-days: 1 + + generate-versions-manifest: + name: Generate versions.dmm + needs: discover-architectures + runs-on: ubuntu-latest + permissions: + contents: read + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 # Fetch all history to get all tags + + - name: Generate versions.dmm + run: | + set -e + echo "# List of available versions for dm_sw_ring modules" > versions.dmm + echo "# Generated automatically by CI" >> versions.dmm + echo "" >> versions.dmm + + # Get all version tags (starting with 'v') and extract version numbers + VERSIONS=$(git tag -l 'v*' | sed 's/^v//' | sort -V | tr '\n' ' ' | sed 's/ $//') + + # Remove vlatest from the list if present + VERSIONS=$(echo $VERSIONS | sed 's/\blatest\b//g' | xargs) + + if [ -z "$VERSIONS" ]; then + echo "Warning: No version tags found" + VERSIONS="${{ github.event.release.tag_name }}" + VERSIONS="${VERSIONS#v}" + fi + + echo "Found versions: $VERSIONS" + + # Add $version-available directives for the modules + echo "\$version-available dm_sw_ring $VERSIONS" >> versions.dmm + + echo "Generated versions.dmm:" + cat versions.dmm + + - name: Upload versions.dmm as artifact + uses: actions/upload-artifact@v4.4.3 + with: + name: versions-manifest + path: versions.dmm + retention-days: 1 + + upload-release-assets: + name: Upload Release Assets + needs: [build-release, generate-versions-manifest] + runs-on: ubuntu-latest + permissions: + contents: write + + steps: + - name: Download all artifacts + uses: actions/download-artifact@v4.1.3 + with: + path: artifacts + + - name: Display artifact structure + run: | + echo "Downloaded artifacts:" + ls -lR artifacts/ + + - name: Upload release assets to versioned tag + shell: bash + env: + GH_TOKEN: ${{ github.token }} + run: | + set -e + # Upload all release archives to the GitHub release + shopt -s nullglob + zip_files=(artifacts/release-*/*.zip) + + if [ ${#zip_files[@]} -eq 0 ]; then + echo "Error: No artifacts found to upload" + exit 1 + fi + + for zip_file in "${zip_files[@]}"; do + echo "Uploading $zip_file to ${{ github.event.release.tag_name }}..." + gh release upload ${{ github.event.release.tag_name }} \ + "$zip_file" \ + --repo ${{ github.repository }} \ + --clobber + done + + # Upload versions.dmm to the versioned release + if [ -f artifacts/versions-manifest/versions.dmm ]; then + echo "Uploading versions.dmm to ${{ github.event.release.tag_name }}..." + gh release upload ${{ github.event.release.tag_name }} \ + artifacts/versions-manifest/versions.dmm \ + --repo ${{ github.repository }} \ + --clobber + fi + + echo "Successfully uploaded ${#zip_files[@]} artifact(s) to ${{ github.event.release.tag_name }}" + + - name: Create or update latest release + shell: bash + env: + GH_TOKEN: ${{ github.token }} + run: | + set -e + + # Check if vlatest release exists + if gh release view vlatest --repo ${{ github.repository }} >/dev/null 2>&1; then + echo "Release vlatest exists, deleting it..." + gh release delete vlatest --repo ${{ github.repository }} --yes + fi + + # Create new vlatest release + echo "Creating vlatest release..." + gh release create vlatest \ + --repo ${{ github.repository }} \ + --title "Latest Release (based on ${{ github.event.release.tag_name }})" \ + --notes "This release always points to the latest stable version. Currently based on ${{ github.event.release.tag_name }}." + + - name: Upload release assets to latest tag + shell: bash + env: + GH_TOKEN: ${{ github.token }} + run: | + set -e + shopt -s nullglob + zip_files=(artifacts/release-*/*.zip) + + for zip_file in "${zip_files[@]}"; do + echo "Uploading $zip_file to vlatest..." + gh release upload vlatest \ + "$zip_file" \ + --repo ${{ github.repository }} \ + --clobber + done + + # Upload versions.dmm to the latest release + if [ -f artifacts/versions-manifest/versions.dmm ]; then + echo "Uploading versions.dmm to vlatest..." + gh release upload vlatest \ + artifacts/versions-manifest/versions.dmm \ + --repo ${{ github.repository }} \ + --clobber + fi + + echo "Successfully uploaded ${#zip_files[@]} artifact(s) to vlatest" diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e2843d9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,28 @@ +CMakeLists.txt.user +CMakeCache.txt +CMakeFiles +CMakeScripts +Testing +Makefile +cmake_install.cmake +install_manifest.txt +compile_commands.json +CTestTestfile.cmake +_deps +CMakeUserPresets.json + +# Build directory +build/ +build_*/ +_codeql_build_dir/ +_codeql_detected_source_root +*.dmf +*.dmfc + + +# CLion +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#cmake-build-* diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..86d5026 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,8 @@ +{ + "files.associations": { + "dmfsi.h": "c" + }, + "C_Cpp.default.compileCommands": "${workspaceFolder}/build/compile_commands.json", + "C_Cpp.intelliSenseEngine": "default", + "C_Cpp.errorSquiggles": "enabled" +} diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..65b572a --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,46 @@ +{ + // See https://code.visualstudio.com/docs/editor/tasks for more information + "version": "2.0.0", + "tasks": [ + { + "label": "CMake: Configure", + "type": "shell", + "command": "cmake", + "args": [ + "-S", + "${workspaceFolder}", + "-B", + "${workspaceFolder}/build" + ], + "group": "build", + "options": { + "cwd": "${workspaceFolder}" + }, + "problemMatcher": [] + }, + { + "label": "CMake: Build", + "type": "shell", + "command": "cmake", + "args": [ + "--build", + "${workspaceFolder}/build" + ], + "group": "build", + "problemMatcher": [] + }, + { + "label": "CMake: Clean", + "type": "shell", + "command": "cmake", + "args": [ + "--build", + "${workspaceFolder}/build", + "--target", + "clean" + ], + "group": "build", + "problemMatcher": [] + } + ] +} diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..231de13 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,91 @@ +# ===================================================================== +# DMOD Software Ring Module +# ===================================================================== +cmake_minimum_required(VERSION 3.18) + +# ====================================================================== +# For VS Code +# ====================================================================== +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +# ====================================================================== +# DMOD Software Ring Version +# ====================================================================== +# Allow version to be passed as a parameter, default to 0.1 +if(NOT DEFINED DMOD_MODULE_VERSION) + set(DMOD_MODULE_VERSION "0.1" CACHE STRING "DMOD module version") +endif() + +# ====================================================================== +# Fetch DMOD repository +# ====================================================================== +include(FetchContent) +FetchContent_Declare( + dmod + GIT_REPOSITORY https://github.com/choco-technologies/dmod.git + GIT_TAG develop +) + +# ====================================================================== +# DMOD Configuration +# ====================================================================== +set(DMOD_MODE "DMOD_MODULE" CACHE STRING "DMOD build mode") +set(DMOD_BUILD_TESTS OFF CACHE BOOL "Build tests") +set(DMOD_BUILD_EXAMPLES OFF CACHE BOOL "Build examples") +set(DMOD_BUILD_TOOLS OFF CACHE BOOL "Build tools") +set(DMOD_BUILD_TEMPLATES OFF CACHE BOOL "Build templates") + +FetchContent_MakeAvailable(dmod) + +project(dm_sw_ring + VERSION ${DMOD_MODULE_VERSION} + DESCRIPTION "DMOD Software Ring Module" + LANGUAGES C CXX) +set(DMOD_DIR ${dmod_SOURCE_DIR} CACHE PATH "DMOD source directory") + +# ====================================================================== +# Import dmod functions and macros +# ====================================================================== +set(DMOD_DIR ${dmod_SOURCE_DIR} CACHE PATH "DMOD source directory") +set(DMOD_SCRIPTS_DIR ${DMOD_DIR}/scripts CACHE PATH "DMOD scripts directory") +include(${DMOD_DIR}/paths.cmake) +dmod_setup_external_module() + +# ====================================================================== +# DMOD Software Ring Module Configuration +# ====================================================================== +# Name of the module +set(DMOD_MODULE_NAME dm_sw_ring) + +# Version is already set above and used in project() +# No need to set it again here + +# Author (should be string) +set(DMOD_AUTHOR_NAME "Patryk Kubiak") + +# Stack size for the module (should be integer) +set(DMOD_STACK_SIZE 1024) + +# Path to the DMR file for this module +set(DMOD_DMR_PATH ${CMAKE_CURRENT_SOURCE_DIR}/dm_sw_ring.dmr) + +# +# dmod_add_library - create a library module +# it has the same signature as add_library +# and can be used in the same way after the creation +# (for example, to link libraries) +# +dmod_add_library(${DMOD_MODULE_NAME} ${DMOD_MODULE_VERSION} + # List of source files - can include C and C++ files + src/dm_sw_ring.c +) + +target_include_directories(${DMOD_MODULE_NAME} PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/include +) + +# ====================================================================== +# test_dm_sw_ring Application +# ====================================================================== +# Add test_dm_sw_ring application subdirectory +# add_subdirectory(apps/test_dm_sw_ring) diff --git a/dm_sw_ring.dmr b/dm_sw_ring.dmr new file mode 100644 index 0000000..54c7dd6 --- /dev/null +++ b/dm_sw_ring.dmr @@ -0,0 +1,30 @@ +# DMOD Resource File for DMOD Software Ring Module +# This file specifies where resources should be installed + +# === Core Module === +# Main module file - always installed +dmf=./${module}.dmf => ${destination}/${module}.dmf [origin=${dmf_dir}/${module}.dmf] + +# Compressed module +dmfc=./${module}.dmfc => ${destination}/${module}.dmfc [origin=${build_dir}/dmfc/${module}.dmfc] + +# Dependencies file (if exists) +dmd=./${module}.dmd => ${destination}/${module}.dmd [origin=${dmf_dir}/${module}.dmd] + +# Version information +version=./${module}_version.txt => ${destination}/${module}_version.txt [origin=${dmf_dir}/${module}_version.txt] + +# === Documentation === +# Module documentation in markdown format for dmf-man tool +docs=./docs => ${destination}/${module}/docs [origin=${repo_dir}/docs] + +# README file +readme=./README.md => ${destination}/${module}/README.md [origin=${repo_dir}/README.md] + +# === Header Files === +# Include directory with all headers (dm_sw_ring.h and dm_sw_ring_defs.h) +inc=./include => ${destination}/${module}/include [origin=${repo_dir}/include] [origin=${build_dir}/${module}_defs.h] + +# === License === +# License file +license=./LICENSE => ${destination}/${module}/LICENSE [origin=${repo_dir}/LICENSE] diff --git a/include/dm_sw_ring.h b/include/dm_sw_ring.h new file mode 100644 index 0000000..08ab035 --- /dev/null +++ b/include/dm_sw_ring.h @@ -0,0 +1,135 @@ +#ifndef DM_SW_RING_H +#define DM_SW_RING_H + +#include +#include +#include "dmod_types.h" +#include "dm_sw_ring_defs.h" + +// ============================================================================ +// Types +// ============================================================================ +/** + * @brief Opaque handle to a software ring buffer instance + */ +typedef struct dm_sw_ring* dm_sw_ring_t; + +/** + * @brief Type for ring buffer capacity (number of elements) + */ +typedef uint32_t dm_sw_ring_capacity_t; + +/** + * @brief Flags for ring buffer behavior + */ +typedef enum +{ + dm_sw_ring_flags_drop_old_data = (1 << 0), //!< Whether to drop old data when the buffer is full + dm_sw_ring_flags_mutex_sync = (1 << 1), //!< Whether to use mutex for synchronization + dm_sw_ring_flags_wait_for_space = (1 << 2), //!< Whether to wait for space to become available when writing to a full buffer + dm_sw_ring_flags_wait_for_data = (1 << 3), //!< Whether to wait for some data to become available when reading from an empty buffer + dm_sw_ring_flags_wait_for_some_data = dm_sw_ring_flags_wait_for_data, //!< Whether to wait for at least some data to become available when reading from an empty buffer + dm_sw_ring_flags_wait_for_all_data = dm_sw_ring_flags_wait_for_data | (1 << 4), //!< Whether to wait for all requested data to become available when reading from an empty buffer + + dm_sw_ring_flags_default = dm_sw_ring_flags_drop_old_data + | dm_sw_ring_flags_mutex_sync + | dm_sw_ring_flags_wait_for_space + | dm_sw_ring_flags_wait_for_some_data //!< Default flags for ring buffer behavior +} dm_sw_ring_flags_t; + +// ============================================================================ +// Module Interface Declarations +// ============================================================================ + +/** + * @brief Create a software ring buffer instance + * @param capacity The capacity of the ring buffer (number of elements) + * @param flags Flags for ring buffer behavior + * @return A handle to the created ring buffer instance, or NULL on failure + */ +dmod_dm_sw_ring_api(1.0, dm_sw_ring_t, _create, (dm_sw_ring_capacity_t capacity, dm_sw_ring_flags_t flags)); + +/** + * @brief Destroy a software ring buffer instance + * @param ring The handle to the ring buffer instance to destroy + */ +dmod_dm_sw_ring_api(1.0, void, _destroy, (dm_sw_ring_t ring)); + +/** + * @brief Write data to the ring buffer + * @param ring The handle to the ring buffer instance + * @param data Pointer to the data to write + * @param length The number of elements to write + * @return The number of elements actually written, or 0 on failure + */ +dmod_dm_sw_ring_api(1.0, dm_sw_ring_capacity_t, _write, (dm_sw_ring_t ring, const void* data, dm_sw_ring_capacity_t length)); + +/** + * @brief Read data from the ring buffer + * @param ring The handle to the ring buffer instance + * @param buffer Pointer to the buffer to store the read data + * @param length The maximum number of elements to read + * @return The number of elements actually read, or 0 on failure + */ +dmod_dm_sw_ring_api(1.0, dm_sw_ring_capacity_t, _read, (dm_sw_ring_t ring, void* buffer, dm_sw_ring_capacity_t length)); + +/** + * @brief Get the capacity of the ring buffer + * @param ring The handle to the ring buffer instance + * @return The capacity of the ring buffer (number of elements) + */ +dmod_dm_sw_ring_api(1.0, dm_sw_ring_capacity_t, _capacity, (dm_sw_ring_t ring)); + +/** + * @brief Get the number of elements currently stored in the ring buffer + * @param ring The handle to the ring buffer instance + * @return The number of elements currently stored in the ring buffer + */ +dmod_dm_sw_ring_api(1.0, dm_sw_ring_capacity_t, _size, (dm_sw_ring_t ring)); + +/** + * @brief Get the number of free spaces available in the ring buffer + * @param ring The handle to the ring buffer instance + * @return The number of free spaces available in the ring buffer + */ +dmod_dm_sw_ring_api(1.0, dm_sw_ring_capacity_t, _available_space, (dm_sw_ring_t ring)); + +/** + * @brief Peek at the data in the ring buffer without removing it + * @param ring The handle to the ring buffer instance + * @param buffer Pointer to the buffer to store the peeked data + * @param length The maximum number of elements to peek + * @return The number of elements actually peeked, or a negative error code on failure + */ +dmod_dm_sw_ring_api(1.0, int32_t, _peek, (dm_sw_ring_t ring, void* buffer, dm_sw_ring_capacity_t length)); + +/** + * @brief Discard the oldest data from the ring buffer without reading it + * @param ring The handle to the ring buffer instance + * @param length The maximum number of elements to discard + * @return The number of elements actually discarded, or a negative error code on failure + */ +dmod_dm_sw_ring_api(1.0, int32_t, _discard, (dm_sw_ring_t ring, dm_sw_ring_capacity_t length)); + +/** + * @brief Clear the contents of the ring buffer + * @param ring The handle to the ring buffer instance + * @return 0 on success, or a negative error code on failure + */ +dmod_dm_sw_ring_api(1.0, int32_t, _clear, (dm_sw_ring_t ring)); + +/** + * @brief Check if the ring buffer is full + * @param ring The handle to the ring buffer instance + * @return true if the ring buffer is full, false otherwise + */ +dmod_dm_sw_ring_api(1.0, bool, _is_full, (dm_sw_ring_t ring)); + +/** + * @brief Check if the ring buffer is empty + * @param ring The handle to the ring buffer instance + * @return true if the ring buffer is empty, false otherwise + */ +dmod_dm_sw_ring_api(1.0, bool, _is_empty, (dm_sw_ring_t ring)); + +#endif // DM_SW_RING_H \ No newline at end of file diff --git a/manifest.dmm b/manifest.dmm new file mode 100644 index 0000000..4009c3f --- /dev/null +++ b/manifest.dmm @@ -0,0 +1,8 @@ +# Include dynamically generated versions list from latest release +$include https://github.com/choco-technologies/dm_sw_ring/releases/download/vlatest/versions.dmm + +# Module entries with version placeholder - will be expanded by $version-available +dm_sw_ring https://github.com/choco-technologies/dm_sw_ring/releases/download/v/dm_sw_ring-v-.zip + +# Test application +# test_dm_sw_ring https://github.com/choco-technologies/dm_sw_ring/releases/download/v/dm_sw_ring-v-.zip diff --git a/src/dm_sw_ring.c b/src/dm_sw_ring.c new file mode 100644 index 0000000..5a8ed9a --- /dev/null +++ b/src/dm_sw_ring.c @@ -0,0 +1,713 @@ +#include "dmod.h" +#include "dm_sw_ring.h" + +// ============================================================================ +// Local Types and Definitions +// ============================================================================ +#define DM_SW_RING_MAGIC 0x52494E47 // 'RING' in ASCII +#define DM_SW_RING_MAX_SIZE INT32_MAX + +/** + * @brief Internal structure representing a software ring buffer instance + */ +struct dm_sw_ring +{ + uint32_t magic; // Magic number for validation + dm_sw_ring_capacity_t capacity; // Capacity of the ring buffer (number of elements) + dm_sw_ring_capacity_t head; // Index of the head (next element to read) + dm_sw_ring_capacity_t tail; // Index of the tail (next element to write) + uint8_t* buffer; // Pointer to the buffer memory + dm_sw_ring_flags_t flags; // Flags for ring buffer behavior + void* mutex; // Mutex for synchronization (if enabled) + void* data_semaphore; // Semaphore for waiting on data availability (if enabled) + void* space_semaphore; // Semaphore for waiting on space availability (if enabled) +}; + +// ============================================================================ +// Local prototypes +// ============================================================================ + +static void ring_cleanup(dm_sw_ring_t ring); +static bool lock_ring(dm_sw_ring_t ring); +static void unlock_ring(dm_sw_ring_t ring); +static bool validate_ring(dm_sw_ring_t ring); +static dm_sw_ring_capacity_t available_space(dm_sw_ring_t ring); +static dm_sw_ring_capacity_t available_data(dm_sw_ring_t ring); +static bool is_full(dm_sw_ring_t ring); +static bool is_empty(dm_sw_ring_t ring); +static void put_byte(dm_sw_ring_t ring, uint8_t data); +static uint8_t get_byte(dm_sw_ring_t ring); +static void discard(dm_sw_ring_t ring, dm_sw_ring_capacity_t length); +static dm_sw_ring_capacity_t prepare_space(dm_sw_ring_t ring, dm_sw_ring_capacity_t length); +static dm_sw_ring_capacity_t write_data(dm_sw_ring_t ring, const uint8_t* data, dm_sw_ring_capacity_t length); +static dm_sw_ring_capacity_t read_data(dm_sw_ring_t ring, uint8_t* buffer, dm_sw_ring_capacity_t length); +static dm_sw_ring_capacity_t peek_data(dm_sw_ring_t ring, uint8_t* buffer, dm_sw_ring_capacity_t length); +static bool wait_for_space(dm_sw_ring_t ring, dm_sw_ring_capacity_t length); +static bool wait_for_data(dm_sw_ring_t ring, dm_sw_ring_capacity_t length); +static void signal_space(dm_sw_ring_t ring, dm_sw_ring_capacity_t length); +static void signal_data(dm_sw_ring_t ring, dm_sw_ring_capacity_t length); + +// ============================================================================ +// Module Interface Implementation +// ============================================================================ + +/** + * @brief Module initialization (optional) + */ +void dmod_preinit(void) +{ + // Nothing to do +} + +/** + * @brief Module initialization + */ +int dmod_init(const Dmod_Config_t *Config) +{ + (void)Config; + return 0; +} + +/** + * @brief Module deinitialization + */ +void dmod_deinit(void) +{ + // Nothing to do +} + +// ============================================================================ +// Interface Implementation +// ============================================================================ + +dmod_dm_sw_ring_api_declaration(1.0, dm_sw_ring_t, _create, (dm_sw_ring_capacity_t capacity, dm_sw_ring_flags_t flags)) +{ + dm_sw_ring_t ring = NULL; + + if (capacity == 0 || capacity > DM_SW_RING_MAX_SIZE) + { + DMOD_LOG_ERROR("Ring buffer capacity must be greater than zero and less than or equal to %d\n", DM_SW_RING_MAX_SIZE); + return NULL; + } + + ring = Dmod_Malloc(sizeof(struct dm_sw_ring)); + if (ring == NULL) + { + DMOD_LOG_ERROR("Failed to allocate memory for ring buffer instance\n"); + return NULL; + } + + ring->buffer = Dmod_Malloc(capacity); + if (ring->buffer == NULL) + { + DMOD_LOG_ERROR("Failed to allocate memory for ring buffer data\n"); + ring_cleanup(ring); + return NULL; + } + + ring->magic = DM_SW_RING_MAGIC; + ring->capacity = capacity; + ring->head = 0; + ring->tail = 0; + ring->flags = flags; + ring->mutex = NULL; + ring->space_semaphore = NULL; + ring->data_semaphore = NULL; + + if(flags & dm_sw_ring_flags_mutex_sync) + { + ring->mutex = Dmod_Mutex_New(true); + if (ring->mutex == NULL) + { + DMOD_LOG_ERROR("Failed to create mutex for ring buffer synchronization\n"); + ring_cleanup(ring); + return NULL; + } + } + + if(flags & dm_sw_ring_flags_wait_for_data) + { + dm_sw_ring_capacity_t max_count = flags & dm_sw_ring_flags_wait_for_all_data ? capacity : 1; + ring->data_semaphore = Dmod_Semaphore_New(0, max_count); + if (ring->data_semaphore == NULL) + { + DMOD_LOG_ERROR("Failed to create semaphore for data availability\n"); + ring_cleanup(ring); + return NULL; + } + } + + if(flags & dm_sw_ring_flags_wait_for_space) + { + ring->space_semaphore = Dmod_Semaphore_New(ring->capacity, ring->capacity); + if (ring->space_semaphore == NULL) + { + DMOD_LOG_ERROR("Failed to create semaphore for space availability\n"); + ring_cleanup(ring); + return NULL; + } + } + + return ring; +} + +dmod_dm_sw_ring_api_declaration(1.0, void, _destroy, (dm_sw_ring_t ring)) +{ + if (lock_ring(ring)) + { + ring->magic = 0; + + if (ring->mutex != NULL) + { + Dmod_Mutex_Delete(ring->mutex); + } + + Dmod_Free(ring->buffer); + Dmod_Free(ring); + + // No need to unlock since the instance is being destroyed + } +} + +dmod_dm_sw_ring_api_declaration(1.0, dm_sw_ring_capacity_t, _write, (dm_sw_ring_t ring, const void* data, dm_sw_ring_capacity_t length)) +{ + dm_sw_ring_capacity_t written = 0; + const uint8_t* input = (const uint8_t*)data; + + if ((input == NULL) && (length > 0)) + { + DMOD_LOG_ERROR("The given data buffer is NULL or length is zero\n"); + return 0; + } + + if (lock_ring(ring)) + { + while(written < length) + { + dm_sw_ring_capacity_t to_send = length - written; + dm_sw_ring_capacity_t available = prepare_space(ring, to_send); + + if (available == 0) + { + break; // No more space available, exit the loop + } + + written += write_data(ring, input + written, available); + } + + unlock_ring(ring); + } + + return written; +} + +dmod_dm_sw_ring_api_declaration(1.0, dm_sw_ring_capacity_t, _read, (dm_sw_ring_t ring, void* buffer, dm_sw_ring_capacity_t length)) +{ + dm_sw_ring_capacity_t all_read = 0; + + if ((buffer == NULL) && (length > 0)) + { + DMOD_LOG_ERROR("Invalid output buffer\n"); + return 0; + } + + if (lock_ring(ring)) + { + while(all_read < length) + { + dm_sw_ring_capacity_t to_read = length - all_read; + dm_sw_ring_capacity_t read = read_data(ring, (uint8_t*)buffer + all_read, to_read); + all_read += read; + + if (read == 0 && !(ring->flags & dm_sw_ring_flags_wait_for_all_data)) + { + break; // No more data available, exit the loop + } + } + unlock_ring(ring); + } + + return all_read; +} + +dmod_dm_sw_ring_api_declaration(1.0, dm_sw_ring_capacity_t, _capacity, (dm_sw_ring_t ring)) +{ + dm_sw_ring_capacity_t capacity = 0; + if(lock_ring(ring)) + { + capacity = ring->capacity; + unlock_ring(ring); + } + return capacity; +} + +dmod_dm_sw_ring_api_declaration(1.0, dm_sw_ring_capacity_t, _size, (dm_sw_ring_t ring)) +{ + dm_sw_ring_capacity_t size = 0; + if(lock_ring(ring)) + { + if (ring->tail >= ring->head) + { + size = ring->tail - ring->head; + } + else + { + size = ring->capacity - (ring->head - ring->tail); + } + unlock_ring(ring); + } + return size; +} + +dmod_dm_sw_ring_api_declaration(1.0, dm_sw_ring_capacity_t, _available_space, (dm_sw_ring_t ring)) +{ + dm_sw_ring_capacity_t space = 0; + if(lock_ring(ring)) + { + space = available_space(ring); + unlock_ring(ring); + } + return space; +} + +dmod_dm_sw_ring_api_declaration(1.0, int32_t, _peek, (dm_sw_ring_t ring, void* buffer, dm_sw_ring_capacity_t length)) +{ + int32_t peeked = -1; + dm_sw_ring_capacity_t index = 0; + + if ((buffer == NULL) && (length > 0)) + { + DMOD_LOG_ERROR("Invalid output buffer\n"); + return -1; + } + + if (lock_ring(ring)) + { + peeked = (int32_t)peek_data(ring, (uint8_t*)buffer, length); + unlock_ring(ring); + } + + return peeked; +} + +dmod_dm_sw_ring_api_declaration(1.0, int32_t, _discard, (dm_sw_ring_t ring, dm_sw_ring_capacity_t length)) +{ + int32_t discarded = -1; + if(length == 0) + { + return 0; // No-op if length is zero + } + if(lock_ring(ring)) + { + length = length > ring->capacity ? ring->capacity : length; + discard(ring, length); + discarded = (int32_t)length; + unlock_ring(ring); + } + + return discarded; +} + +dmod_dm_sw_ring_api_declaration(1.0, int32_t, _clear, (dm_sw_ring_t ring)) +{ + int32_t result = -1; + if(lock_ring(ring)) + { + ring->head = 0; + ring->tail = 0; + result = 0; // Success + unlock_ring(ring); + } + return result; +} + +dmod_dm_sw_ring_api_declaration(1.0, bool, _is_full, (dm_sw_ring_t ring)) +{ + bool full = false; + if(lock_ring(ring)) + { + full = is_full(ring); + unlock_ring(ring); + } + return full; +} + +dmod_dm_sw_ring_api_declaration(1.0, bool, _is_empty, (dm_sw_ring_t ring)) +{ + bool empty = false; + if(lock_ring(ring)) + { + empty = is_empty(ring); + unlock_ring(ring); + } + return empty; +} + +// ============================================================================ +// Local prototypes implementation +// ============================================================================ + +/** + * @brief Clean up resources associated with a ring buffer instance + * @param ring The handle to the ring buffer instance to clean up + */ +static void ring_cleanup(dm_sw_ring_t ring) +{ + if (ring->mutex != NULL) + { + Dmod_Mutex_Delete(ring->mutex); + ring->mutex = NULL; + } + if (ring->data_semaphore != NULL) + { + Dmod_Semaphore_Delete(ring->data_semaphore); + ring->data_semaphore = NULL; + } + if (ring->space_semaphore != NULL) + { + Dmod_Semaphore_Delete(ring->space_semaphore); + ring->space_semaphore = NULL; + } + if (ring->buffer != NULL) + { + Dmod_Free(ring->buffer); + ring->buffer = NULL; + } +} + +/** + * @brief Lock the ring buffer for exclusive access (if mutex synchronization is enabled) + * @param ring The handle to the ring buffer instance + * @return true if the lock was acquired successfully, false otherwise + */ +static bool lock_ring(dm_sw_ring_t ring) +{ + bool success = false; + Dmod_EnterCritical(); + void* mutex = NULL; + if(validate_ring(ring)) + { + mutex = ring->mutex; + success = true; + } + else + { + DMOD_LOG_ERROR("Invalid ring buffer instance\n"); + } + Dmod_ExitCritical(); + if(mutex != NULL && Dmod_Mutex_Lock(ring->mutex) != 0) + { + DMOD_LOG_ERROR("Failed to acquire mutex lock for ring buffer\n"); + success = false; + } + return success; +} + +/** + * @brief Unlock the ring buffer after exclusive access (if mutex synchronization is enabled) + * @param ring The handle to the ring buffer instance + */ +static void unlock_ring(dm_sw_ring_t ring) +{ + if(ring->mutex != NULL) + { + Dmod_Mutex_Unlock(ring->mutex); + } + else + { + Dmod_ExitCritical(); + } +} + +/** + * @brief Validate a ring buffer instance + * @param ring The handle to the ring buffer instance to validate + * @return true if the ring buffer instance is valid, false otherwise + */ +static bool validate_ring(dm_sw_ring_t ring) +{ + return (ring != NULL) && (ring->magic == DM_SW_RING_MAGIC); +} + +/** + * @brief Get the number of free spaces available in the ring buffer + * @param ring The handle to the ring buffer instance + * @return The number of free spaces available in the ring buffer + */ +static dm_sw_ring_capacity_t available_space(dm_sw_ring_t ring) +{ + if (ring->tail >= ring->head) + { + return ring->capacity - (ring->tail - ring->head); + } + else + { + return ring->head - ring->tail; + } +} + +/** + * @brief Get the number of elements currently stored in the ring buffer + * @param ring The handle to the ring buffer instance + * @return The number of elements currently stored in the ring buffer + */ +static dm_sw_ring_capacity_t available_data(dm_sw_ring_t ring) +{ + if (ring->tail >= ring->head) + { + return ring->tail - ring->head; + } + else + { + return ring->capacity - (ring->head - ring->tail); + } +} + +/** + * @brief Get the number of elements currently stored in the ring buffer + * @param ring The handle to the ring buffer instance + * @return The number of elements currently stored in the ring buffer + */ +static bool is_full(dm_sw_ring_t ring) +{ + return available_space(ring) == 0; +} + +/** + * @brief Check if the ring buffer is empty + * @param ring The handle to the ring buffer instance + * @return true if the ring buffer is empty, false otherwise + */ +static bool is_empty(dm_sw_ring_t ring) +{ + return ring->head == ring->tail; +} + +/** + * @brief Write a single byte to the ring buffer (unsafe, caller must ensure space is available) + * @param ring The handle to the ring buffer instance + * @param data The byte to write + */ +static void put_byte(dm_sw_ring_t ring, uint8_t data) +{ + ring->buffer[ring->tail] = data; + ring->tail = (ring->tail + 1) % ring->capacity; +} + +/** + * @brief Read a single byte from the ring buffer (unsafe, caller must ensure data is available) + * @param ring The handle to the ring buffer instance + * @return The byte read from the ring buffer + */ +static uint8_t get_byte(dm_sw_ring_t ring) +{ + uint8_t data = ring->buffer[ring->head]; + ring->head = (ring->head + 1) % ring->capacity; + return data; +} + +/** + * @brief Discard the oldest data from the ring buffer without reading it (unsafe, caller must ensure data is available) + * @param ring The handle to the ring buffer instance + * @param length The number of elements to discard + */ +static void discard(dm_sw_ring_t ring, dm_sw_ring_capacity_t length) +{ + if (length == 0) + { + return; + } + dm_sw_ring_capacity_t available = available_data(ring); + length = length > available ? available : length; + + wait_for_data(ring, length); + + ring->head = (ring->head + length) % ring->capacity; + +} + +/** + * @brief Prepare space in the ring buffer for writing new data, discarding old data if necessary (unsafe, caller must ensure ring is valid) + * @param ring The handle to the ring buffer instance + * @param length The number of elements to prepare space for + * @return The number of elements that can be written after preparing space, which may be less than the requested length if dropping old data is disabled + */ +static dm_sw_ring_capacity_t prepare_space(dm_sw_ring_t ring, dm_sw_ring_capacity_t length) +{ + length = length > ring->capacity ? ring->capacity : length; + + dm_sw_ring_capacity_t available = available_space(ring); + dm_sw_ring_capacity_t missing = (length > available) ? (length - available) : 0; + + if(missing == 0) + { + available = length; // Enough space available, can write full length + } + else if(ring->flags & dm_sw_ring_flags_drop_old_data) + { + discard(ring, missing); + available += missing; + } + else if(ring->flags & dm_sw_ring_flags_wait_for_space) + { + if(!wait_for_space(ring, missing)) + { + DMOD_LOG_ERROR("Failed to wait for space in ring buffer\n"); + available = 0; // No space available + } + else + { + available = available_space(ring); + } + } + + return available; +} + +/** + * @brief Write data to the ring buffer (unsafe, caller must ensure space is available) + * @param ring The handle to the ring buffer instance + * @param data Pointer to the data to write + * @param length The number of elements to write + * @return The number of elements actually written, which may be less than the requested length if dropping old data is disabled and there is not enough space + */ +static dm_sw_ring_capacity_t write_data(dm_sw_ring_t ring, const uint8_t* data, dm_sw_ring_capacity_t length) +{ + dm_sw_ring_capacity_t written = 0; + + for (written = 0; written < length; written++) + { + if (!wait_for_space(ring, 1)) + { + break; + } + put_byte(ring, data[written]); + } + + signal_data(ring, written); + + return written; +} + +/** + * @brief Read data from the ring buffer (unsafe, caller must ensure data is available) + * @param ring The handle to the ring buffer instance + * @param buffer Pointer to the buffer to store the read data + * @param length The maximum number of elements to read + * @return The number of elements actually read, which may be less than the requested length if + * there is not enough data available in the ring buffer + */ +static dm_sw_ring_capacity_t read_data(dm_sw_ring_t ring, uint8_t* buffer, dm_sw_ring_capacity_t length) +{ + dm_sw_ring_capacity_t read = 0; + bool should_wait = (ring->flags & dm_sw_ring_flags_wait_for_data) != 0; + + for (read = 0; read < length; read++) + { + dm_sw_ring_capacity_t available = available_data(ring); + if (available == 0 && !should_wait) + { + break; // No more data available, exit the loop + } + + if (!wait_for_data(ring, 1)) + { + break; // Buffer is empty + } + buffer[read] = get_byte(ring); + should_wait = (ring->flags & dm_sw_ring_flags_wait_for_all_data) != 0; // If waiting for all data, continue waiting even if some data was read + } + + signal_space(ring, read); + + return read; +} + +/** + * @brief Peek at the data in the ring buffer without removing it (unsafe, caller must ensure data is available) + * @param ring The handle to the ring buffer instance + * @param buffer Pointer to the buffer to store the peeked data + * @param length The maximum number of elements to peek + * @return The number of elements actually peeked, which may be less than the requested length if there is not enough data available in the ring buffer + */ +static dm_sw_ring_capacity_t peek_data(dm_sw_ring_t ring, uint8_t* buffer, dm_sw_ring_capacity_t length) +{ + dm_sw_ring_capacity_t peeked = 0; + dm_sw_ring_capacity_t index = ring->head; + + for (peeked = 0; peeked < length; peeked++) + { + if (index == ring->tail) + { + break; // Buffer is empty + } + buffer[peeked] = ring->buffer[index]; + index = (index + 1) % ring->capacity; + } + + return peeked; +} + +/** + * @brief Wait for space to become available in the ring buffer for writing (if wait_for_space flag is enabled) + * @param ring The handle to the ring buffer instance + * @param length The number of elements to wait for space for + * @return true if space is available or was successfully waited for, false if an error occurred while waiting + */ +static bool wait_for_space(dm_sw_ring_t ring, dm_sw_ring_capacity_t length) +{ + bool success = available_space(ring) >= length; + if (ring->space_semaphore != NULL && !success) + { + unlock_ring(ring); // Unlock before waiting to allow other threads to make progress + success = Dmod_Semaphore_Wait(ring->space_semaphore, length) == 0 + && lock_ring(ring); + } + + return success; +} + +/** + * @brief Wait for data to become available in the ring buffer for reading (if wait_for_data or wait_for_some_data flag is enabled) + * @param ring The handle to the ring buffer instance + * @param length The number of elements to wait for data for + * @return true if data is available or was successfully waited for, false if an error occurred while waiting + */ +static bool wait_for_data(dm_sw_ring_t ring, dm_sw_ring_capacity_t length) +{ + bool success = available_data(ring) >= length; + if (ring->data_semaphore != NULL && !success) + { + unlock_ring(ring); // Unlock before waiting to allow other threads to make progress + success = Dmod_Semaphore_Wait(ring->data_semaphore, length) == 0 + && lock_ring(ring); + } + + return success; +} + +/** + * @brief Signal that space has become available in the ring buffer (if wait_for_space flag is enabled) + * @param ring The handle to the ring buffer instance + * @param length The number of elements of space that have become available + */ +static void signal_space(dm_sw_ring_t ring, dm_sw_ring_capacity_t length) +{ + if (ring->space_semaphore != NULL) + { + Dmod_Semaphore_Post(ring->space_semaphore, length); + } +} + +/** + * @brief Signal that data has become available in the ring buffer (if wait_for_data or wait_for_some_data flag is enabled) + * @param ring The handle to the ring buffer instance + * @param length The number of elements of data that have become available + */ +static void signal_data(dm_sw_ring_t ring, dm_sw_ring_capacity_t length) +{ + if (ring->data_semaphore != NULL) + { + Dmod_Semaphore_Post(ring->data_semaphore, length); + } +} \ No newline at end of file