From a8de53793c54bccc74f4e6378be7eef69b28ad59 Mon Sep 17 00:00:00 2001 From: Patryk Kubiak Date: Wed, 3 Jun 2026 09:59:01 +0200 Subject: [PATCH 1/7] Add initial VSCode configuration files for CMake project setup --- .vscode/settings.json | 8 ++++++++ .vscode/tasks.json | 46 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+) create mode 100644 .vscode/settings.json create mode 100644 .vscode/tasks.json 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": [] + } + ] +} From 5e852610f23d08a885b94f523fd29de8ac031ddf Mon Sep 17 00:00:00 2001 From: Patryk Kubiak Date: Wed, 3 Jun 2026 10:10:56 +0200 Subject: [PATCH 2/7] Initialize DMOD Software Ring Module with CI configuration, CMake setup, and resource definitions --- .github/workflows/ci.yml | 41 +++++++++++++++++++ .gitignore | 28 +++++++++++++ CMakeLists.txt | 87 ++++++++++++++++++++++++++++++++++++++++ dm_sw_ring.dmr | 30 ++++++++++++++ include/dm_sw_ring.h | 0 src/dm_sw_ring.c | 30 ++++++++++++++ 6 files changed, 216 insertions(+) create mode 100644 .github/workflows/ci.yml create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 dm_sw_ring.dmr create mode 100644 include/dm_sw_ring.h create mode 100644 src/dm_sw_ring.c 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/.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/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..b6e87ac --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,87 @@ +# ===================================================================== +# DMOD Software Ring Module +# ===================================================================== +cmake_minimum_required(VERSION 3.18) + + +# ====================================================================== +# 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..e69de29 diff --git a/src/dm_sw_ring.c b/src/dm_sw_ring.c new file mode 100644 index 0000000..cff586f --- /dev/null +++ b/src/dm_sw_ring.c @@ -0,0 +1,30 @@ +#include "dmod.h" + +// ============================================================================ +// 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) +{ + // Nothing to do + return 0; +} + +/** + * @brief Module deinitialization + */ +void dmod_deinit(void) +{ + // Nothing to do +} From 3b0f858fdc8f223f1e34f8fe33c82ed9125f3c8a Mon Sep 17 00:00:00 2001 From: Patryk Kubiak Date: Wed, 3 Jun 2026 10:13:18 +0200 Subject: [PATCH 3/7] Added manifest --- manifest.dmm | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 manifest.dmm 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 From 56cd1ad4799fa79056ce43190c9c23b023dc4d0d Mon Sep 17 00:00:00 2001 From: Patryk Kubiak Date: Wed, 3 Jun 2026 10:19:52 +0200 Subject: [PATCH 4/7] Add GitHub Actions workflow for release management --- .github/workflows/release.yml | 249 ++++++++++++++++++++++++++++++++++ 1 file changed, 249 insertions(+) create mode 100644 .github/workflows/release.yml 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" From 3db2cbedda8324d373479c4215638b75466352f8 Mon Sep 17 00:00:00 2001 From: Patryk Kubiak Date: Wed, 3 Jun 2026 11:01:35 +0200 Subject: [PATCH 5/7] Implement software ring buffer structure and API functions --- CMakeLists.txt | 4 + include/dm_sw_ring.h | 116 +++++++++++++++++++ src/dm_sw_ring.c | 263 ++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 382 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b6e87ac..231de13 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,6 +3,10 @@ # ===================================================================== cmake_minimum_required(VERSION 3.18) +# ====================================================================== +# For VS Code +# ====================================================================== +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) # ====================================================================== # DMOD Software Ring Version diff --git a/include/dm_sw_ring.h b/include/dm_sw_ring.h index e69de29..ceda0ce 100644 --- a/include/dm_sw_ring.h +++ b/include/dm_sw_ring.h @@ -0,0 +1,116 @@ +#ifndef DM_SW_RING_H +#define DM_SW_RING_H + +#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; + +// ============================================================================ +// Module Interface Declarations +// ============================================================================ + +/** + * @brief Create a software ring buffer instance + * @param capacity The capacity of the ring buffer (number of elements) + * @param drop_old_data Whether to drop old data when the buffer is full + * @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, bool drop_old_data)); + +/** + * @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 a negative error code on failure + */ +dmod_dm_sw_ring_api(1.0, int32_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 a negative error code on failure + */ +dmod_dm_sw_ring_api(1.0, int32_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, int32_t, _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, int32_t, _is_empty, (dm_sw_ring_t ring)); + +#endif // DM_SW_RING_H \ No newline at end of file diff --git a/src/dm_sw_ring.c b/src/dm_sw_ring.c index cff586f..4a25163 100644 --- a/src/dm_sw_ring.c +++ b/src/dm_sw_ring.c @@ -1,4 +1,29 @@ #include "dmod.h" +#include "dm_sw_ring.h" + +// ============================================================================ +// Local Types and Definitions +// ============================================================================ +#define DM_SW_RING_MAGIC 0x52494E47 // 'RING' in ASCII + +/** + * @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 + bool drop_old_data; // Whether to drop old data when the buffer is full +}; + +// ============================================================================ +// Local prototypes +// ============================================================================ + +static bool validate_ring(dm_sw_ring_t ring); // ============================================================================ // Module Interface Implementation @@ -17,7 +42,7 @@ void dmod_preinit(void) */ int dmod_init(const Dmod_Config_t *Config) { - // Nothing to do + (void)Config; return 0; } @@ -28,3 +53,239 @@ 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, bool drop_old_data)) +{ + dm_sw_ring_t ring = NULL; + + if (capacity == 0) + { + DMOD_LOG_ERROR("Ring buffer capacity must be greater than zero\n"); + 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"); + Dmod_Free(ring); + return NULL; + } + + ring->magic = DM_SW_RING_MAGIC; + ring->capacity = capacity; + ring->head = 0; + ring->tail = 0; + ring->drop_old_data = drop_old_data; + + return ring; +} + +dmod_dm_sw_ring_api_declaration(1.0, void, _destroy, (dm_sw_ring_t ring)) +{ + Dmod_EnterCritical(); + + if (validate_ring(ring)) + { + ring->magic = 0; + + Dmod_Free(ring->buffer); + Dmod_Free(ring); + } + else + { + DMOD_LOG_ERROR("Invalid ring buffer instance\n"); + } + + Dmod_ExitCritical(); +} + +dmod_dm_sw_ring_api_declaration(1.0, int32_t, _write, (dm_sw_ring_t ring, const void* data, dm_sw_ring_capacity_t length)) +{ + int32_t written = -1; + const uint8_t* input = (const uint8_t*)data; + + if ((input == NULL) && (length > 0)) + { + DMOD_LOG_ERROR("Invalid data buffer\n"); + return -1; + } + + Dmod_EnterCritical(); + + if (validate_ring(ring)) + { + + } + else + { + DMOD_LOG_ERROR("Invalid ring buffer instance\n"); + } + + Dmod_ExitCritical(); + return written; +} + +dmod_dm_sw_ring_api_declaration(1.0, int32_t, _read, (dm_sw_ring_t ring, void* buffer, dm_sw_ring_capacity_t length)) +{ + int32_t read = -1; + + if ((buffer == NULL) && (length > 0)) + { + DMOD_LOG_ERROR("Invalid output buffer\n"); + return -1; + } + + Dmod_EnterCritical(); + + if (validate_ring(ring)) + { + + } + else + { + DMOD_LOG_ERROR("Invalid ring buffer instance\n"); + } + + Dmod_ExitCritical(); + return read; +} + +dmod_dm_sw_ring_api_declaration(1.0, dm_sw_ring_capacity_t, _capacity, (dm_sw_ring_t ring)) +{ + if (!validate_ring(ring)) + { + DMOD_LOG_ERROR("Invalid ring buffer instance\n"); + return 0; + } + + return ring->capacity; +} + +dmod_dm_sw_ring_api_declaration(1.0, dm_sw_ring_capacity_t, _size, (dm_sw_ring_t ring)) +{ + if (!validate_ring(ring)) + { + DMOD_LOG_ERROR("Invalid ring buffer instance\n"); + return 0; + } + + return 0; +} + +dmod_dm_sw_ring_api_declaration(1.0, dm_sw_ring_capacity_t, _available_space, (dm_sw_ring_t ring)) +{ + if (!validate_ring(ring)) + { + DMOD_LOG_ERROR("Invalid ring buffer instance\n"); + return 0; + } + + return 0; +} + +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; + } + + Dmod_EnterCritical(); + + if (validate_ring(ring)) + { + + } + else + { + DMOD_LOG_ERROR("Invalid ring buffer instance\n"); + } + + Dmod_ExitCritical(); + 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; + + Dmod_EnterCritical(); + + if (validate_ring(ring)) + { + discarded = (int32_t)discard_unsafe(ring, length); + } + else + { + DMOD_LOG_ERROR("Invalid ring buffer instance\n"); + } + + Dmod_ExitCritical(); + return discarded; +} + +dmod_dm_sw_ring_api_declaration(1.0, int32_t, _clear, (dm_sw_ring_t ring)) +{ + if (!validate_ring(ring)) + { + DMOD_LOG_ERROR("Invalid ring buffer instance\n"); + return -1; + } + + ring->head = 0; + ring->tail = 0; + + return 0; +} + +dmod_dm_sw_ring_api_declaration(1.0, int32_t, _is_full, (dm_sw_ring_t ring)) +{ + if (!validate_ring(ring)) + { + DMOD_LOG_ERROR("Invalid ring buffer instance\n"); + return -1; + } + + return 0; +} + +dmod_dm_sw_ring_api_declaration(1.0, int32_t, _is_empty, (dm_sw_ring_t ring)) +{ + if (!validate_ring(ring)) + { + DMOD_LOG_ERROR("Invalid ring buffer instance\n"); + return -1; + } + + return 0; +} + +// ============================================================================ +// Local prototypes implementation +// ============================================================================ + +/** + * @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); +} \ No newline at end of file From b656d2992c8623ac7ae99164be5e0c9b91e37ff7 Mon Sep 17 00:00:00 2001 From: Patryk Kubiak Date: Wed, 3 Jun 2026 12:22:01 +0200 Subject: [PATCH 6/7] Enhance software ring buffer with flag-based behavior and mutex synchronization --- include/dm_sw_ring.h | 25 ++- src/dm_sw_ring.c | 398 ++++++++++++++++++++++++++++++++++--------- 2 files changed, 334 insertions(+), 89 deletions(-) diff --git a/include/dm_sw_ring.h b/include/dm_sw_ring.h index ceda0ce..06ff998 100644 --- a/include/dm_sw_ring.h +++ b/include/dm_sw_ring.h @@ -2,6 +2,7 @@ #define DM_SW_RING_H #include +#include #include "dmod_types.h" #include "dm_sw_ring_defs.h" @@ -18,6 +19,22 @@ typedef struct dm_sw_ring* dm_sw_ring_t; */ 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 block on write when the buffer is full + dm_sw_ring_flags_wait_for_data = (1 << 3), //!< Whether to block on read when the buffer is empty + + 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_data //!< Default flags for ring buffer behavior +} dm_sw_ring_flags_t; + // ============================================================================ // Module Interface Declarations // ============================================================================ @@ -25,10 +42,10 @@ typedef uint32_t dm_sw_ring_capacity_t; /** * @brief Create a software ring buffer instance * @param capacity The capacity of the ring buffer (number of elements) - * @param drop_old_data Whether to drop old data when the buffer is full + * @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, bool drop_old_data)); +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 @@ -104,13 +121,13 @@ dmod_dm_sw_ring_api(1.0, int32_t, _clear, (dm_sw_ring_t ring)); * @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, int32_t, _is_full, (dm_sw_ring_t ring)); +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, int32_t, _is_empty, (dm_sw_ring_t ring)); +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/src/dm_sw_ring.c b/src/dm_sw_ring.c index 4a25163..69d07e8 100644 --- a/src/dm_sw_ring.c +++ b/src/dm_sw_ring.c @@ -5,6 +5,7 @@ // 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 @@ -16,14 +17,27 @@ struct dm_sw_ring 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 - bool drop_old_data; // Whether to drop old data when the buffer is full + dm_sw_ring_flags_t flags; // Flags for ring buffer behavior + void* mutex; // Mutex for synchronization (if enabled) }; // ============================================================================ // Local prototypes // ============================================================================ +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 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); // ============================================================================ // Module Interface Implementation @@ -58,13 +72,13 @@ void dmod_deinit(void) // Interface Implementation // ============================================================================ -dmod_dm_sw_ring_api_declaration(1.0, dm_sw_ring_t, _create, (dm_sw_ring_capacity_t capacity, bool drop_old_data)) +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) + if (capacity == 0 || capacity > DM_SW_RING_MAX_SIZE) { - DMOD_LOG_ERROR("Ring buffer capacity must be greater than zero\n"); + 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; } @@ -83,32 +97,47 @@ dmod_dm_sw_ring_api_declaration(1.0, dm_sw_ring_t, _create, (dm_sw_ring_capacity return 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"); + Dmod_Free(ring->buffer); + Dmod_Free(ring); + return NULL; + } + } + else + { + ring->mutex = NULL; + } + ring->magic = DM_SW_RING_MAGIC; ring->capacity = capacity; ring->head = 0; ring->tail = 0; - ring->drop_old_data = drop_old_data; + ring->flags = flags; return ring; } dmod_dm_sw_ring_api_declaration(1.0, void, _destroy, (dm_sw_ring_t ring)) { - Dmod_EnterCritical(); - - if (validate_ring(ring)) + if (lock_ring(ring)) { ring->magic = 0; + if (ring->mutex != NULL) + { + Dmod_Mutex_Delete(ring->mutex); + } + Dmod_Free(ring->buffer); Dmod_Free(ring); - } - else - { - DMOD_LOG_ERROR("Invalid ring buffer instance\n"); - } - Dmod_ExitCritical(); + // No need to unlock since the instance is being destroyed + } } dmod_dm_sw_ring_api_declaration(1.0, int32_t, _write, (dm_sw_ring_t ring, const void* data, dm_sw_ring_capacity_t length)) @@ -122,18 +151,15 @@ dmod_dm_sw_ring_api_declaration(1.0, int32_t, _write, (dm_sw_ring_t ring, const return -1; } - Dmod_EnterCritical(); - - if (validate_ring(ring)) - { - - } - else + if (lock_ring(ring)) { - DMOD_LOG_ERROR("Invalid ring buffer instance\n"); + length = prepare_space(ring, length); + + written = (int32_t)write_data(ring, input, length); + + unlock_ring(ring); } - Dmod_ExitCritical(); return written; } @@ -147,52 +173,54 @@ dmod_dm_sw_ring_api_declaration(1.0, int32_t, _read, (dm_sw_ring_t ring, void* b return -1; } - Dmod_EnterCritical(); - - if (validate_ring(ring)) + if (lock_ring(ring)) { - - } - else - { - DMOD_LOG_ERROR("Invalid ring buffer instance\n"); + read = (int32_t)read_data(ring, (uint8_t*)buffer, length); + + unlock_ring(ring); } - Dmod_ExitCritical(); return read; } dmod_dm_sw_ring_api_declaration(1.0, dm_sw_ring_capacity_t, _capacity, (dm_sw_ring_t ring)) { - if (!validate_ring(ring)) + dm_sw_ring_capacity_t capacity = 0; + if(lock_ring(ring)) { - DMOD_LOG_ERROR("Invalid ring buffer instance\n"); - return 0; + capacity = ring->capacity; + unlock_ring(ring); } - - return ring->capacity; + return capacity; } dmod_dm_sw_ring_api_declaration(1.0, dm_sw_ring_capacity_t, _size, (dm_sw_ring_t ring)) { - if (!validate_ring(ring)) + dm_sw_ring_capacity_t size = 0; + if(lock_ring(ring)) { - DMOD_LOG_ERROR("Invalid ring buffer instance\n"); - return 0; + if (ring->tail >= ring->head) + { + size = ring->tail - ring->head; + } + else + { + size = ring->capacity - (ring->head - ring->tail); + } + unlock_ring(ring); } - - return 0; + return size; } dmod_dm_sw_ring_api_declaration(1.0, dm_sw_ring_capacity_t, _available_space, (dm_sw_ring_t ring)) { - if (!validate_ring(ring)) + dm_sw_ring_capacity_t space = 0; + if(lock_ring(ring)) { - DMOD_LOG_ERROR("Invalid ring buffer instance\n"); - return 0; + space = available_space(ring); + unlock_ring(ring); } - - return 0; + 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)) @@ -206,80 +234,116 @@ dmod_dm_sw_ring_api_declaration(1.0, int32_t, _peek, (dm_sw_ring_t ring, void* b return -1; } - Dmod_EnterCritical(); - - if (validate_ring(ring)) - { - - } - else + if (lock_ring(ring)) { - DMOD_LOG_ERROR("Invalid ring buffer instance\n"); + peeked = (int32_t)peek_data(ring, (uint8_t*)buffer, length); + unlock_ring(ring); } - Dmod_ExitCritical(); 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; - - Dmod_EnterCritical(); - - if (validate_ring(ring)) + if(length == 0) { - discarded = (int32_t)discard_unsafe(ring, length); + return 0; // No-op if length is zero } - else + if(lock_ring(ring)) { - DMOD_LOG_ERROR("Invalid ring buffer instance\n"); + length = length > ring->capacity ? ring->capacity : length; + discard(ring, length); + discarded = (int32_t)length; + unlock_ring(ring); } - - Dmod_ExitCritical(); + return discarded; } dmod_dm_sw_ring_api_declaration(1.0, int32_t, _clear, (dm_sw_ring_t ring)) { - if (!validate_ring(ring)) + int32_t result = -1; + if(lock_ring(ring)) { - DMOD_LOG_ERROR("Invalid ring buffer instance\n"); - return -1; + ring->head = 0; + ring->tail = 0; + result = 0; // Success + unlock_ring(ring); } - - ring->head = 0; - ring->tail = 0; - - return 0; + return result; } -dmod_dm_sw_ring_api_declaration(1.0, int32_t, _is_full, (dm_sw_ring_t ring)) +dmod_dm_sw_ring_api_declaration(1.0, bool, _is_full, (dm_sw_ring_t ring)) { - if (!validate_ring(ring)) + bool full = false; + if(lock_ring(ring)) { - DMOD_LOG_ERROR("Invalid ring buffer instance\n"); - return -1; + full = is_full(ring); + unlock_ring(ring); } - - return 0; + return full; } -dmod_dm_sw_ring_api_declaration(1.0, int32_t, _is_empty, (dm_sw_ring_t ring)) +dmod_dm_sw_ring_api_declaration(1.0, bool, _is_empty, (dm_sw_ring_t ring)) { - if (!validate_ring(ring)) + bool empty = false; + if(lock_ring(ring)) { - DMOD_LOG_ERROR("Invalid ring buffer instance\n"); - return -1; + empty = is_empty(ring); + unlock_ring(ring); } - - return 0; + return empty; } // ============================================================================ // Local prototypes implementation // ============================================================================ +/** + * @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 @@ -288,4 +352,168 @@ dmod_dm_sw_ring_api_declaration(1.0, int32_t, _is_empty, (dm_sw_ring_t ring)) 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 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) +{ + 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; + + if(ring->flags & dm_sw_ring_flags_drop_old_data) + { + discard(ring, missing); + available += missing; + } + + 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 (is_full(ring)) + { + break; + } + put_byte(ring, data[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; + + for (read = 0; read < length; read++) + { + if (is_empty(ring)) + { + break; // Buffer is empty + } + buffer[read] = get_byte(ring); + } + + 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; } \ No newline at end of file From 855078039fb80b6dd691529e46c81479a40df7c7 Mon Sep 17 00:00:00 2001 From: Patryk Kubiak Date: Wed, 3 Jun 2026 14:32:08 +0200 Subject: [PATCH 7/7] Enhance software ring buffer with semaphore support for data and space management --- include/dm_sw_ring.h | 16 +-- src/dm_sw_ring.c | 246 ++++++++++++++++++++++++++++++++++++++----- 2 files changed, 229 insertions(+), 33 deletions(-) diff --git a/include/dm_sw_ring.h b/include/dm_sw_ring.h index 06ff998..08ab035 100644 --- a/include/dm_sw_ring.h +++ b/include/dm_sw_ring.h @@ -26,13 +26,15 @@ 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 block on write when the buffer is full - dm_sw_ring_flags_wait_for_data = (1 << 3), //!< Whether to block on read when the buffer is empty + 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_data //!< Default flags for ring buffer behavior + | dm_sw_ring_flags_wait_for_some_data //!< Default flags for ring buffer behavior } dm_sw_ring_flags_t; // ============================================================================ @@ -58,18 +60,18 @@ dmod_dm_sw_ring_api(1.0, void, _destroy, (dm_sw_ring_t ring)); * @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 a negative error code on failure + * @return The number of elements actually written, or 0 on failure */ -dmod_dm_sw_ring_api(1.0, int32_t, _write, (dm_sw_ring_t ring, const void* data, dm_sw_ring_capacity_t length)); +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 a negative error code on failure + * @return The number of elements actually read, or 0 on failure */ -dmod_dm_sw_ring_api(1.0, int32_t, _read, (dm_sw_ring_t ring, void* buffer, dm_sw_ring_capacity_t length)); +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 diff --git a/src/dm_sw_ring.c b/src/dm_sw_ring.c index 69d07e8..5a8ed9a 100644 --- a/src/dm_sw_ring.c +++ b/src/dm_sw_ring.c @@ -19,16 +19,20 @@ struct dm_sw_ring 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); @@ -38,6 +42,10 @@ static dm_sw_ring_capacity_t prepare_space(dm_sw_ring_t ring, dm_sw_ring_capacit 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 @@ -93,31 +101,52 @@ dmod_dm_sw_ring_api_declaration(1.0, dm_sw_ring_t, _create, (dm_sw_ring_capacity if (ring->buffer == NULL) { DMOD_LOG_ERROR("Failed to allocate memory for ring buffer data\n"); - Dmod_Free(ring); + 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"); - Dmod_Free(ring->buffer); - Dmod_Free(ring); + ring_cleanup(ring); return NULL; } } - else + + if(flags & dm_sw_ring_flags_wait_for_data) { - ring->mutex = NULL; + 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; + } } - ring->magic = DM_SW_RING_MAGIC; - ring->capacity = capacity; - ring->head = 0; - ring->tail = 0; - ring->flags = flags; + 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; } @@ -140,22 +169,31 @@ dmod_dm_sw_ring_api_declaration(1.0, void, _destroy, (dm_sw_ring_t ring)) } } -dmod_dm_sw_ring_api_declaration(1.0, int32_t, _write, (dm_sw_ring_t ring, const void* data, dm_sw_ring_capacity_t length)) +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)) { - int32_t written = -1; + dm_sw_ring_capacity_t written = 0; const uint8_t* input = (const uint8_t*)data; if ((input == NULL) && (length > 0)) { - DMOD_LOG_ERROR("Invalid data buffer\n"); - return -1; + DMOD_LOG_ERROR("The given data buffer is NULL or length is zero\n"); + return 0; } if (lock_ring(ring)) { - length = prepare_space(ring, length); + while(written < length) + { + dm_sw_ring_capacity_t to_send = length - written; + dm_sw_ring_capacity_t available = prepare_space(ring, to_send); - written = (int32_t)write_data(ring, input, length); + if (available == 0) + { + break; // No more space available, exit the loop + } + + written += write_data(ring, input + written, available); + } unlock_ring(ring); } @@ -163,24 +201,33 @@ dmod_dm_sw_ring_api_declaration(1.0, int32_t, _write, (dm_sw_ring_t ring, const return written; } -dmod_dm_sw_ring_api_declaration(1.0, int32_t, _read, (dm_sw_ring_t ring, void* buffer, dm_sw_ring_capacity_t length)) +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)) { - int32_t read = -1; + dm_sw_ring_capacity_t all_read = 0; if ((buffer == NULL) && (length > 0)) { DMOD_LOG_ERROR("Invalid output buffer\n"); - return -1; + return 0; } if (lock_ring(ring)) { - read = (int32_t)read_data(ring, (uint8_t*)buffer, length); - + 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 read; + return all_read; } dmod_dm_sw_ring_api_declaration(1.0, dm_sw_ring_capacity_t, _capacity, (dm_sw_ring_t ring)) @@ -300,6 +347,34 @@ dmod_dm_sw_ring_api_declaration(1.0, bool, _is_empty, (dm_sw_ring_t ring)) // 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 @@ -371,6 +446,23 @@ static dm_sw_ring_capacity_t available_space(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 + */ +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 @@ -421,7 +513,17 @@ static uint8_t get_byte(dm_sw_ring_t ring) */ 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; + } /** @@ -435,13 +537,29 @@ static dm_sw_ring_capacity_t prepare_space(dm_sw_ring_t ring, dm_sw_ring_capacit length = length > ring->capacity ? ring->capacity : length; dm_sw_ring_capacity_t available = available_space(ring); - dm_sw_ring_capacity_t missing = length - available; + dm_sw_ring_capacity_t missing = (length > available) ? (length - available) : 0; - if(ring->flags & dm_sw_ring_flags_drop_old_data) + 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; } @@ -459,13 +577,15 @@ static dm_sw_ring_capacity_t write_data(dm_sw_ring_t ring, const uint8_t* data, for (written = 0; written < length; written++) { - if (is_full(ring)) + if (!wait_for_space(ring, 1)) { break; } put_byte(ring, data[written]); } + signal_data(ring, written); + return written; } @@ -480,16 +600,26 @@ static dm_sw_ring_capacity_t write_data(dm_sw_ring_t ring, const uint8_t* data, 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++) { - if (is_empty(ring)) + 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; } @@ -516,4 +646,68 @@ static dm_sw_ring_capacity_t peek_data(dm_sw_ring_t ring, uint8_t* buffer, dm_sw } 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