diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..ebcac08 --- /dev/null +++ b/.clang-format @@ -0,0 +1,239 @@ +# Available style options are described in https://clang.llvm.org/docs/ClangFormatStyleOptions.html +# +# An easy way to create the .clang-format file is: +# +# clang-format -style=llvm -dump-config > .clang-format +# +Language: Cpp +BasedOnStyle: LLVM +AccessModifierOffset: -1 +AlignAfterOpenBracket: Align +AlignArrayOfStructures: None +AlignConsecutiveAssignments: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: true + PadOperators: true +AlignConsecutiveBitFields: + Enabled: true + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: true + PadOperators: true +AlignConsecutiveDeclarations: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + PadOperators: false +AlignConsecutiveMacros: + Enabled: true + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + PadOperators: false +AlignConsecutiveShortCaseStatements: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCaseColons: false +AlignEscapedNewlines: Left +AlignOperands: Align +AlignTrailingComments: + Kind: Leave + OverEmptyLines: 1 +AllowAllArgumentsOnNextLine: false +AllowAllParametersOfDeclarationOnNextLine: false +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortEnumsOnASingleLine: false +AllowShortFunctionsOnASingleLine: Inline +AllowShortIfStatementsOnASingleLine: false +AllowShortLambdasOnASingleLine: All +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: MultiLine +AttributeMacros: + - __capability +BinPackArguments: true +BinPackParameters: true +BitFieldColonSpacing: Both +BreakBeforeBraces: Custom +BraceWrapping: + AfterCaseLabel: true + AfterClass: true + AfterControlStatement: Always + AfterEnum: true + AfterExternBlock: false + AfterFunction: true + AfterNamespace: true + AfterObjCDeclaration: true + AfterStruct: true + AfterUnion: true + BeforeCatch: true + BeforeElse: true + BeforeLambdaBody: false + BeforeWhile: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakAfterAttributes: Never +BreakAfterJavaFieldAnnotations: false +BreakArrays: false +BreakBeforeBinaryOperators: None +BreakBeforeConceptDeclarations: Always +BreakBeforeInlineASMColon: OnlyMultiline +BreakBeforeTernaryOperators: true +BreakConstructorInitializers: AfterColon +BreakInheritanceList: AfterColon +BreakStringLiterals: true +ColumnLimit: 0 +CommentPragmas: "^ IWYU pragma:" +CompactNamespaces: false +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: false +DerivePointerAlignment: false +DisableFormat: false +EmptyLineAfterAccessModifier: Never +EmptyLineBeforeAccessModifier: Always +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: true +ForEachMacros: + - foreach + - Q_FOREACH + - BOOST_FOREACH +IfMacros: + - KJ_IF_MAYBE +IncludeBlocks: Preserve +IncludeCategories: + - Regex: '^"(llvm|llvm-c|clang|clang-c)/' + Priority: 2 + SortPriority: 0 + CaseSensitive: false + - Regex: '^(<|"(gtest|gmock|isl|json)/)' + Priority: 3 + SortPriority: 0 + CaseSensitive: false + - Regex: ".*" + Priority: 1 + SortPriority: 0 + CaseSensitive: false +IncludeIsMainRegex: "(Test)?$" +IncludeIsMainSourceRegex: "" +IndentAccessModifiers: false +IndentCaseBlocks: false +IndentCaseLabels: false +IndentExternBlock: NoIndent +IndentGotoLabels: true +IndentPPDirectives: None +IndentRequiresClause: true +IndentWidth: 4 +IndentWrappedFunctionNames: false +InsertBraces: false +InsertNewlineAtEOF: true +InsertTrailingCommas: None +IntegerLiteralSeparator: + Binary: 0 + BinaryMinDigits: 0 + Decimal: 0 + DecimalMinDigits: 0 + Hex: 0 + HexMinDigits: 0 +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: false +KeepEmptyLinesAtEOF: true +LambdaBodyIndentation: Signature +LineEnding: DeriveLF +MacroBlockBegin: "" +MacroBlockEnd: "" +MaxEmptyLinesToKeep: 2 +NamespaceIndentation: All +ObjCBinPackProtocolList: Auto +ObjCBlockIndentWidth: 2 +ObjCBreakBeforeNestedBlockParam: true +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: true +PackConstructorInitializers: BinPack +PenaltyBreakAssignment: 1000 +PenaltyBreakBeforeFirstCallParameter: 19 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakOpenParenthesis: 0 +PenaltyBreakString: 1000 +PenaltyBreakTemplateDeclaration: 10 +PenaltyExcessCharacter: 1000000 +PenaltyIndentedWhitespace: 0 +PenaltyReturnTypeOnItsOwnLine: 1000 +PointerAlignment: Right +PPIndentWidth: 4 +QualifierAlignment: Leave +ReferenceAlignment: Pointer +ReflowComments: false +RemoveBracesLLVM: false +RemoveParentheses: Leave +RemoveSemicolon: false +RequiresClausePosition: OwnLine +RequiresExpressionIndentation: OuterScope +SeparateDefinitionBlocks: Leave +ShortNamespaceLines: 1 +SortIncludes: Never +SortJavaStaticImport: Before +SortUsingDeclarations: LexicographicNumeric +SpaceAfterCStyleCast: false +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: true +SpaceAroundPointerQualifiers: Both +SpaceBeforeAssignmentOperators: true +SpaceBeforeCaseColon: false +SpaceBeforeCpp11BracedList: false +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeJsonColon: false +SpaceBeforeParens: ControlStatements +SpaceBeforeParensOptions: + AfterControlStatements: true + AfterForeachMacros: true + AfterFunctionDefinitionName: false + AfterFunctionDeclarationName: false + AfterIfMacros: true + AfterOverloadedOperator: false + AfterRequiresInClause: false + AfterRequiresInExpression: false + BeforeNonEmptyParentheses: false +SpaceBeforeRangeBasedForLoopColon: true +SpaceBeforeSquareBrackets: false +SpaceInEmptyBlock: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: Never +SpacesInContainerLiterals: true +SpacesInLineCommentPrefix: + Minimum: 1 + Maximum: -1 +SpacesInParens: Never +SpacesInParensOptions: + InCStyleCasts: false + InConditionalStatements: false + InEmptyParentheses: false + Other: false +SpacesInSquareBrackets: false +Standard: Latest +StatementAttributeLikeMacros: + - Q_EMIT +StatementMacros: + - Q_UNUSED + - QT_REQUIRE_VERSION +TabWidth: 4 +UseTab: Never +VerilogBreakBetweenInstancePorts: true +WhitespaceSensitiveMacros: + - BOOST_PP_STRINGIZE + - CF_SWIFT_NAME + - NS_SWIFT_NAME + - PP_STRINGIZE + - STRINGIZE \ No newline at end of file diff --git a/README.md b/README.md index a447dfb..d6e12a6 100644 --- a/README.md +++ b/README.md @@ -1,373 +1,199 @@ -# **Device Parameters** +# Device Parameters -The **Device Parameters** module manages all device parameters through a single configuration table, offering a streamlined approach to system configuration and diagnostics. This module often serves as the backbone of an embedded system, controlling the application's behavior and providing insights into device performance, making diagnostics straightforward and efficient. +`Device Parameters` is a portable embedded C module for managing runtime parameters, validation, metadata, and optional NVM persistence from a single parameter definition table. -For more details about storage into NVM look at the [General Embedded C Library Manual](https://github.com/GeneralEmbeddedCLibraries/documentation/blob/develop/General_Embedded_C_Library_Manual.pdf) document. +It is designed for projects that need a clean way to: -## Key Benefits -- **Centralized Configuration**: Simplifies management by defining all system settings in a single table. -- **Data Integrity**: Integrated Min/Max range checking and custom validation callbacks. -- **Thread Safety**: Optional mutex protection for multi-threaded environments (e.g., RTOS). -- **Persistence**: Transparent NVM (Non-Volatile Memory) integration with CRC and signature verification. -- **Observability**: Built-in support for parameter names, units, and descriptions for easy CLI/GUI integration. -- **Event-Driven**: Chained callback system for notifying other modules of parameter changes. +- define parameters once +- read and write them through a consistent API +- expose them to CLI, PC tools, or protocol layers by stable external IDs +- validate values before they are accepted +- persist selected values to NVM +- keep memory usage predictable on embedded targets -## Integration with other modules in General Embedded C Libraries ecosystem -When combined with the following modules, the **Device Parameters** module significantly enhances the capabilities of embedded applications: +## What this module provides -1. **[CLI (Command Line Interface)](https://github.com/GeneralEmbeddedCLibraries/cli)** - - Enables communication between the embedded application and a PC. - - Allows real-time configuration and diagnostics via the CLI interface. +- **Single source of truth** through `par_table.def` +- **Typed APIs** for `U8`, `I8`, `U16`, `I16`, `U32`, `I32`, and, when enabled, `F32` +- **Optional metadata** such as name, unit, description, access, ID, persistence flags, and optional role-based read/write visibility metadata +- **Validation pipeline** with compile-time checks for integer ranges and optional runtime hooks for dynamic rules +- **Static live-value storage** grouped by width instead of heap allocation +- **Fast external lookup by ID** through a compile-time generated static hash map +- **Optional NVM integration** for persistent parameters +- **Portable core + platform hooks** for RTOS, mutex, logging, assertions, and atomic backends -2. **[NVM (Non-Volatile Memory)](https://github.com/GeneralEmbeddedCLibraries/nvm)** - - Ensures that configured settings are stored persistently. +## Start here -3. **Device Parameters + CLI + NVM** - - Together, these modules allow: - - Communication with a PC using CLI. - - Configuration and diagnostics of the application via Device Parameters. - - Storage of settings to NVM, ensuring data persistence. +### Quick start -### Development Benefits -By leveraging this combination of modules, embedded firmware development becomes significantly faster and more efficient, enabling developers to focus on functionality rather than repetitive low-level implementation. +1. Add `src/par.c` and the sources from `src/def`, `src/layout`, `src/persist`, and `src/port` to your build. Add `src/persist/backend` only when you use the packaged backend adapter. +2. Provide a project-specific `par_table.def` at the package root. +3. Provide `port/par_cfg_port.h` in your include path. +4. Optionally provide `port/par_if_port.c` and `port/par_atomic_port.h` when your platform needs them. +5. Call `par_init()` before using runtime APIs. +6. Use the typed `par_set_*` / `par_get_*` APIs in application code. Getter APIs now use an explicit output pointer and return `par_status_t`. When `PAR_CFG_ENABLE_ACCESS = 1`, checked public read APIs also reject parameters that do not expose external read capability. -## **Dependencies** +A minimal example: -### **1. NVM Module** -In case of using NVM module *PAR_CFG_NVM_EN = 1*, then [NVM module](https://github.com/GeneralEmbeddedCLibraries/nvm) must pe part of project. -NVM module must take following path: -``` -"root/middleware/nvm/nvm/src/nvm.h" -``` +This quick-start example assumes `PAR_CFG_ENABLE_TYPE_F32 = 1`. -### **2. C11 compiler support** -Parameter module utilize C11 *_Atomic* and *_Generic* features, therefore make sure your compiler supports C11 primitives. +```c +#include "par.h" -## **Limitations** - - **Heap Usage:** The module uses malloc during par_init() to allocate RAM space for the parameters based on the configuration table. Ensure your heap is sufficiently sized. - - **Alignment:** Address offsets are calculated based on 4-byte (32-bit) alignment to satisfy most ARM Cortex-M requirements. - - **Flat ID Space:** Parameter IDs must be unique across the entire table to ensure NVM consistency. - - **Execution Time:** If many callbacks are chained to a single parameter, the par_set execution time will increase accordingly. - -## **General Embedded C Libraries Ecosystem** -In order to be part of *General Embedded C Libraries Ecosystem* this module must be placed in following path: -``` -root/middleware/parameters/parameters/"module_space" -``` - - ## **API** -| API Functions | Description | Prototype | -| --- | ----------- | ----- | -| **par_init** | Initialization of parameters module | par_status_t par_init(void) |**** -| **par_deinit** | De-initialization of parameters module | par_status_t par_deinit(void) |**** -| **par_is_init** | Get initialization flag | bool par_is_init(void) | - - ## **Setting parameter value API** -| API Functions | Description | Prototype | -| --- | ----------- | ----- | -| **par_set** | Set parameter value | par_status_t par_set(const par_num_t par_num, const void *p_val) | -| **par_set_by_id** | Set parameter value by ID | par_status_t par_set_by_id(const uint16_t id, const void * p_val) | -| **par_set_u8** | Set u8 parameter value | par_status_t par_set_u8(const par_num_t par_num, const uint8_t val) | -| **par_set_i8** | Set i8 parameter value | par_status_t par_set_i8(const par_num_t par_num, const int8_t val) | -| **par_set_u16** | Set u16 parameter value | par_status_t par_set_u16(const par_num_t par_num, const uint16_t val) | -| **par_set_i16** | Set i16 parameter value | par_status_t par_set_i16(const par_num_t par_num, const int16_t val) | -| **par_set_u32** | Set u32 parameter value | par_status_t par_set_u32(const par_num_t par_num, const uint32_t val) | -| **par_set_i32** | Set i32 parameter value | par_status_t par_set_i32(const par_num_t par_num, const int32_t val) | -| **par_set_f32** | Set f32 parameter value | par_status_t par_set_f32(const par_num_t par_num, const float32_t val) | -| **par_set_u8_fast** | Set u8 parameter value fast | par_status_t par_set_u8_fast(const par_num_t par_num, const uint8_t val) | -| **par_set_i8_fast** | Set i8 parameter value fast | par_status_t par_set_i8_fast(const par_num_t par_num, const int8_t val) | -| **par_set_u16_fast** | Set u16 parameter value fast | par_status_t par_set_u16_fast(const par_num_t par_num, const uint16_t val) | -| **par_set_i16_fast** | Set i16 parameter value fast | par_status_t par_set_i16_fast(const par_num_t par_num, const int16_t val) | -| **par_set_u32_fast** | Set u32 parameter value fast | par_status_t par_set_u32_fast(const par_num_t par_num, const uint32_t val) | -| **par_set_i32_fast** | Set i32 parameter value fast | par_status_t par_set_i32_fast(const par_num_t par_num, const int32_t val) | -| **par_set_f32_fast** | Set f32 parameter value fast | par_status_t par_set_f32_fast(const par_num_t par_num, const float32_t val) | -| **par_set_to_default** | Set parameter to default value | par_status_t par_set_to_default (const par_num_t par_num) | -| **par_set_all_to_default** | Set all parameters to default value | par_status_t par_set_all_to_default (void) | -| **PAR_SET** | Set generic parameters value | #define PAR_SET(par_num, value) | - - ## **Getting parameter value API** -| API Functions | Description | Prototype | -| --- | ----------- | ----- | -| **par_get** | Get parameter value | par_status_t par_get (const par_num_t par_num, void *const p_val)| -| **par_get_id** | Get parameter ID number | par_status_t par_get_id(const par_num_t par_num, uint16_t *const p_id) | -| **par_get_u8** | Get u8 parameter value | uint8_t par_get_u8(const par_num_t par_num) | -| **par_get_i8** | Get i8 parameter value | uint8_t par_get_i8(const par_num_t par_num) | -| **par_get_u16** | Get u16 parameter value | uint16_t par_get_u16(const par_num_t par_num) | -| **par_get_i16** | Get i16 parameter value | uint16_t par_get_i16(const par_num_t par_num) | -| **par_get_u32** | Get u32 parameter value | uint32_t par_get_u32(const par_num_t par_num) | -| **par_get_i32** | Get i32 parameter value | uint32_t par_get_i32(const par_num_t par_num) | -| **par_get_f32** | Get f32 parameter value | float32_t par_get_f32(const par_num_t par_num) | -| **par_get_default** | Get default parameter value | par_status_t par_get_default(const par_num_t par_num, void * const p_val) | -| **par_is_changed** | Is parameter value changed from default | bool par_is_changed(const par_num_t par_num) | -| **PAR_GET** | Get generic parameters value | #define PAR_GET(par_num, dest) | - - ## **Parameter configurations API** -| API Functions | Description | Prototype | -| --- | ----------- | ----- | -| **par_get_config** | Get parameter configurations | const par_cfg_t * par_get_config(const par_num_t par_num) | -| **par_get_name** | Get parameter name | const char * par_get_name(const par_num_t par_num) | -| **par_get_range** | Get parameter range | par_range_t par_get_range(const par_num_t par_num) | -| **par_get_unit** | Get parameter unit | const char * par_get_unit(const par_num_t par_num) | -| **par_get_desc** | Get parameter describtion | const char * par_get_desc(const par_num_t par_num) | -| **par_get_type** | Get parameter data type | par_type_list_t par_get_type(const par_num_t par_num) | -| **par_get_access** | Get parameter access | par_access_t par_get_access(const par_num_t par_num) | -| **par_is_persistant** | Get parameter persistance | bool par_is_persistant(const par_num_t par_num) | -| **par_get_num_by_id** | Get parameter number (enumeration) by its ID | par_status_t par_get_num_by_id(const uint16_t id, par_num_t *const p_par_num) | -| **par_get_id_by_num** | Get parameter ID by number (enumeration) | par_status_t par_get_id_by_num(const par_num_t par_num, uint16_t * const p_id) | - - ## **Parameter NVM storage API** -| API Functions | Description | Prototype | -| --- | ----------- | ----- | -| **par_set_n_save** | Set and store parameter to NVM | par_status_t par_set_n_save(const par_num_t par_num, const void * p_val) | -| **par_save_all** | Store all parameters to NVM | par_status_t par_save_all(void) | -| **par_save** | Store single parameter | par_status_t par_save(const par_num_t par_num) | -| **par_save_by_id** | Store single parameter by ID | par_status_t par_save_by_id(const uint16_t par_id) | -| **par_save_clean** | Re-Write complete NVM memory | par_status_t par_save_clean(void) | - - ## **Parameter registrations API** -| API Functions | Description | Prototype | -| --- | ----------- | ----- | -| **par_register_on_change_cb** | Register on value change callback | par_status_t par_register_on_change_cb(const par_on_change_cb_t * const cb) | -| **par_register_validation** | Register value validation callback | par_status_t par_register_validation(const par_validation_t * const validation) | - -## Usage - -**Put all user code between sections: USER CODE BEGIN & USER CODE END!** - -**1. Copy template files to root directory of module.** -**2. List names of all wanted parameters inside **par_cfg.h** file** - -```C -/** - * List of device parameters - * - * @note User shall provide parameter name here as it would be using - * later inside code. - * - * @note User shall change code only inside section of "USER_CODE_BEGIN" - * ans "USER_CODE_END". - */ -typedef enum +static void app_init(void) { - // USER CODE START... - - ePAR_TEST_U8 = 0, - ePAR_TEST_I8, - - ePAR_TEST_U16, - ePAR_TEST_I16, - - ePAR_TEST_U32, - ePAR_TEST_I32, - - ePAR_TEST_F32, - - // USER CODE END... - - ePAR_NUM_OF -} par_num_t; -``` - -**3. Change parameter configuration table inside **par_cfg.c** file. It is recommended to use designated initializers.** - -```C -/** - * Parameters definitions - * - * @brief - * - * Each defined parameter has following properties: - * - * i) Parameter ID: Unique parameter identification number. ID shall not be duplicated. - * ii) Name: Parameter name. Max. length of 32 chars. - * iii) Min: Parameter minimum value. Min value must be less than max value. - * iv) Max: Parameter maximum value. Max value must be more than min value. - * v) Def: Parameter default value. Default value must lie between interval: [min, max] - * vi) Unit: In case parameter shows physical value. Max. length of 32 chars. - * vii) Data type: Parameter data type. Supported types: uint8_t, int8_t, uint16_t, int16_t, uint32_t, int32_t and float32_t - * viii) Access: Access type visible from external device such as PC. Either ReadWrite or ReadOnly. - * ix) Persistence: Tells if parameter value is being written into NVM. - * - * @note User shall fill up wanted parameter definitions! - */ -static const par_cfg_t g_par_table[ePAR_NUM_OF] = -{ - - // USER CODE BEGIN... - - // ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - // ID Name Min Max Def Unit Data type PC Access Persistent Description - // ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - - [ePAR_TEST_U8] = { .id = 0, .name = "Test_u8", .min.u8 = 0, .max.u8 = 10, .def.u8 = 8, .unit = "n/a", .type = ePAR_TYPE_U8, .access = ePAR_ACCESS_RW, .persistant = true, .desc = "Test parameter U8" }, - [ePAR_TEST_I8] = { .id = 1, .name = "Test_i8", .min.i8 = -10, .max.i8 = 100, .def.i8 = -8, .unit = "n/a", .type = ePAR_TYPE_I8, .access = ePAR_ACCESS_RW, .persistant = true, .desc = "Test parameter" }, - - [ePAR_TEST_U16] = { .id = 2, .name = "Test_u16", .min.u16 = 0, .max.u16 = 10, .def.u16 = 3, .unit = "n/a", .type = ePAR_TYPE_U16, .access = ePAR_ACCESS_RW, .persistant = true, .desc = "Test parameter U16"}, - [ePAR_TEST_I16] = { .id = 3, .name = "Test_i16", .min.i16 = -10, .max.i16 = 100, .def.i16 = -5, .unit = "n/a", .type = ePAR_TYPE_I16, .access = ePAR_ACCESS_RW, .persistant = true, .desc = "Test parameter I16"}, - - [ePAR_TEST_U32] = { .id = 4, .name = "Test_u32", .min.u32 = 0, .max.u32 = 10, .def.u32 = 10, .unit = "n/a", .type = ePAR_TYPE_U32, .access = ePAR_ACCESS_RW, .persistant = true, .desc = "Test parameter U32" }, - [ePAR_TEST_I32] = { .id = 5, .name = "Test_i32", .min.i32 = -10, .max.i32 = 100, .def.i32 = -10, .unit = "n/a", .type = ePAR_TYPE_I32, .access = ePAR_ACCESS_RW, .persistant = true, .desc = "Test parameter I32" }, - - [ePAR_TEST_F32] = { .id = 6, .name = "Test_f32", .min.f32 = -10, .max.f32 = 100, .def.f32 = -1.123, .unit = "n/a", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RW, .persistant = true, .desc = "Test parameter F32" }, - - // ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ - - - // USER CODE END... -}; -``` - -**4. Set-up all configurations options inside **par_cfg.h** file** - -| Configuration | Description | -| --- | --- | -| **PAR_CFG_NVM_EN** | Enable/Disable usage of NVM for persistant parameters. | -| **PAR_CFG_NVM_REGION** | Select NVM region for Device Parameter storage space. | -| **PAR_CFG_DEBUG_EN** | Enable/Disable debugging mode. | -| **PAR_CFG_ASSERT_EN** | Enable/Disable asserts. Shall be disabled in release build! | -| **PAR_DBG_PRINT** | Definition of debug print. | -| **PAR_ASSERT** | Definition of assert. | - -**5. Call **par_init()** function** - -```C -// Init parameters -if ( ePAR_OK != par_init()) -{ - PROJECT_CONFIG_ASSERT( 0 ); + if (par_init() != ePAR_OK) + { + /* Handle initialization error */ + } + + (void)par_set_f32(ePAR_CH1_REF_VAL, (float32_t)25.0f); + + float32_t ref_val = 0.0f; + if (par_get_f32(ePAR_CH1_REF_VAL, &ref_val) != ePAR_OK) + { + /* Handle read error */ + } } ``` -**NOTICE: NVM module will be initialized as a part of Device Parameters initialization routine in case of usage (*PAR_CFG_NVM_EN = 1*)!** - -**6. Setting/Getting parameter value** -```C -// Set battery voltage & sytem current -// NOTICE: When using "par_set" don't forget to always CAST to appropriate data type! -(void) par_set( ePAR_BAT_VOLTAGE, (float32_t*) &g_pwr_data.bat.voltage_filt ); -(void) par_set( ePAR_SYS_CURRENT, (float32_t*) &g_pwr_data.inp.sys_cur ); -// Or equivalent to "par_set" -PAR_SET( ePAR_BAT_VOLTAGE, g_pwr_data.bat.voltage_filt ); -PAR_SET( ePAR_SYS_CURRENT, g_pwr_data.inp.sys_cur ); - -// Set and save parameter -if ( ePAR_OK != par_set_n_save( ePAR_P1_10, (uint32_t) &p1_10_val )) -{ - // Operation error... - // Further actions here... -} - -// Get battery voltage & sytem current -// NOTICE: When using "par_get" don't forget to always CAST to appropriate data type! -(void) par_get( ePAR_BAT_VOLTAGE, (float32_t*) &g_pwr_data.bat.voltage_filt ); -(void) par_get( ePAR_SYS_CURRENT, (float32_t*) &g_pwr_data.inp.sys_cur ); - -// Or equivalent to "par_get" -PAR_GET( ePAR_BAT_VOLTAGE, g_pwr_data.bat.voltage_filt ); -PAR_GET( ePAR_SYS_CURRENT, g_pwr_data.inp.sys_cur ); +## Optional role-policy metadata + +When `PAR_CFG_ENABLE_ROLE_POLICY = 1`, each row in `par_table.def` also carries: + +- `read_roles_` +- `write_roles_` + +These fields are **metadata and helper-policy inputs**, not a built-in login/session system. +The package exposes `par_can_read()` / `par_can_write()` so the integration layer +(such as msh, CLI, RPC, or diagnostic service code) can decide which caller roles +are currently active and enforce them consistently. + +The core checked value APIs still consume only the access capability bits (`ePAR_ACCESS_READ` / `ePAR_ACCESS_WRITE`). +Role masks are evaluated only when the integration calls `par_can_read()` / `par_can_write()` or wraps them in a higher-level public interface such as the packaged RT-Thread shell port. +Metadata getters and `par_get_default()` continue to read the configuration table directly and therefore do not consume role-policy metadata. + +`msh info` and `msh json` also show the configured role metadata when this option is enabled. In the packaged RT-Thread shell port, `par get`, `par set`, and the live-value fields printed by `par info` / `par json` are all filtered through the current shell role mask. + +## Documentation map + +- [Getting started](docs/getting-started.md) for integration steps, required files, and first-use examples +- [Architecture](docs/architecture.md) for storage model, validation flow, ID lookup, and layout design +- [API reference](docs/api-reference.md) for the public API grouped by responsibility +- [Flash-ee backend design](docs/flash-ee-backend-design.md) for the portable flash-emulated EEPROM core and adapter model + +## Package layout + +```text +. +├── Kconfig +├── SConscript +├── backend/ +│ ├── par_store_backend_flash_ee_fal.c +│ ├── par_store_backend_flash_ee_native.c +│ └── par_store_backend_rtt_at24cxx.c +├── par_table.def +├── parameters/ +│ ├── README.md +│ ├── CHANGE_LOG.md +│ ├── docs/ +│ │ ├── DeviceParameter_VerificationReport.xlsx +│ │ ├── api-reference.md +│ │ ├── architecture.md +│ │ ├── flash-ee-backend-design.md +│ │ └── getting-started.md +│ ├── src/ +│ │ ├── par.c +│ │ ├── par.h +│ │ ├── par_cfg.h +│ │ ├── def/ +│ │ ├── detail/ +│ │ ├── layout/ +│ │ ├── persist/ +│ │ │ ├── backend/ +│ │ │ │ ├── par_store_backend.h +│ │ │ │ ├── par_store_backend_flash_ee.c +│ │ │ │ ├── par_store_backend_flash_ee.h +│ │ │ │ ├── par_store_backend_flash_ee_cfg.h +│ │ │ │ └── par_store_backend_gel_nvm.c +│ │ │ ├── fnv.h +│ │ │ ├── hash_32a.c +│ │ │ ├── par_nvm.c +│ │ │ ├── par_nvm.h +│ │ │ ├── par_nvm_cfg.h +│ │ │ ├── par_nvm_layout*.c +│ │ │ └── par_nvm_table_id.* +│ │ └── port/ +│ └── template/ +│ ├── par_cfg_port.htmp +│ ├── par_layout_static.htmp +│ └── par_table.deftmp +└── port/ + ├── par_atomic_port.h + ├── par_cfg_port.h + └── par_if_port.c ``` -Setting direct value to parameter: - -```C -par_set( ePAR_BAT_VOLTAGE, (float32_t*) &(float32_t){ 1.1234f} ); +## Required integration files -// Or equivalent using "PAR_SET" -// NOTE: When putting direct numbers using "PAR_SET" always cast to appropriate data type! -PAR_SET( ePAR_BAT_VOLTAGE, (float32_t) 1.1234f ); -``` - -### Normal and fast parameter setting API -When choosing an API for setting parameter values, you must decide between the safe (normal) API and the fast API (with suffix *_fast*). The choice depends on whether your priority is data integrity and system observability or raw execution speed. +This repository contains the reusable module core and templates. A real integration still needs project-owned files. -```C -// --- NORMAL API --- -// Use this for 90% of your code. It prevents errors and triggers callbacks. -par_set_f32( ePAR_TARGET_TEMP, 25.5f ); +### Required -// --- FAST API --- -// Use this in high frequency control loops where every microsecond counts. -// WARNING: No safety checks are performed! -par_set_f32_fast( ePAR_MOTOR_PWM, 0.85f ); -``` +- `par_table.def` at the package root +- `port/par_cfg_port.h` -**7. Store to NVM** +### Optional, depending on configuration -```C -// Store all paramters to NVM -if ( ePAR_OK != par_save_all()) -{ - // Storing to NVM error... - // Further actions here... -} -``` +- `port/par_if_port.c` when `PAR_CFG_IF_PORT_EN = 1` and the target needs stronger platform hooks than the weak defaults in `src/port/par_if.c` +- `port/par_atomic_port.h` when `PAR_ATOMIC_BACKEND = PAR_ATOMIC_BACKEND_PORT` +- generated static layout header when `PAR_CFG_LAYOUT_SOURCE = PAR_CFG_LAYOUT_SCRIPT` +- a concrete storage backend implementation when `PAR_CFG_NVM_EN = 1` -**8. On-change callback usage** - -```C -//////////////////////////////////////////////////////////////////////////////// -/** -* Parameter value change callback -* -* @param[out] par_num - Parameter number -* @param[out] new_val - Parameter new value -* @param[out] new_val - Parameter old value -* @return void -*/ -//////////////////////////////////////////////////////////////////////////////// -void par_on_change_cb1(const par_num_t par_num, const par_type_t new_val, const par_type_t old_val) -{ - cli_printf("Parameter %d change from %d to %d", par_num, old_val.u8, new_val.u8 ); -} +## When to read which document -// Define parameter on change callback for "ePAR_CH1_TEST_MODE_EN" parameter -PAR_DEFINE_ON_CHANGE_CB( test_par_cb, ePAR_CH1_TEST_MODE_EN, par_on_change_cb1); +- Read [Getting started](docs/getting-started.md) when you are integrating the module into a project. +- Read [Architecture](docs/architecture.md) when you need to understand how `par_table.def`, storage groups, layout, validation, or ID lookup work internally. +- Read [API reference](docs/api-reference.md) when you already understand the model and only need function-level guidance. -// Register at init phase -@init -{ - if ( ePAR_OK != par_register_on_change_cb( &test_par_cb )) - { - // Operation error... - // Further actions here... - } -} -``` +## Key integration notes -**9. Parameter value validation usage** - -```C -//////////////////////////////////////////////////////////////////////////////// -/** -* Validate parameter value -* -* @param[in] par_num - Parameter number -* @param[in] val - Parameter new value -* @return true when value valid -*/ -//////////////////////////////////////////////////////////////////////////////// -static bool validate_par_value(const par_num_t par_num, const par_type_t val) -{ - UNUSED(par_num); +- `src/par.h` is the main public entry header. Keep `parameters/src` on the compiler include path so application code can use `#include "par.h"`. +- `par_cfg.h` includes `par_cfg_port.h` unconditionally, so your build must provide that header and make its directory visible to the compiler. +- `PAR_CFG_ENABLE_TYPE_F32` controls whether floating-point parameter support and the related typed APIs are compiled in. +- `PAR_CFG_ENABLE_RUNTIME_VALIDATION` and `PAR_CFG_ENABLE_CHANGE_CALLBACK` control whether normal setters include runtime validation callbacks and on-change callbacks. +- The RT-Thread port exposes leveled package logging hooks (`INFO` / `DEBUG` / `WARN` / `ERROR`). Enable them with `AUTOGEN_PM_USING_DEBUG` in package Kconfig. +- The module separates **internal parameter enumeration** (`par_num_t`) from **external parameter IDs** (`id`). +- The current ID lookup implementation uses a one-entry-per-bucket hash map generated at compile time from `par_table.def`. External IDs must therefore be not only unique, but also collision-free under the configured hash geometry. Optional runtime diagnostic scans can be enabled with `PAR_CFG_ENABLE_RUNTIME_ID_DUP_CHECK` and `PAR_CFG_ENABLE_RUNTIME_ID_HASH_COLLISION_CHECK` when additional startup logs are useful. See `docs/architecture.md` for the collision rule and avoidance guidance. +- Unchecked setter APIs skip runtime validation callbacks and on-change callbacks, so they should be reserved for tightly controlled hot paths. Bitwise fast setters are further restricted to `U8` / `U16` / `U32` flags or bitmask parameters. Legacy `*_fast()` names remain as deprecated aliases. +- NVM support is optional. When enabled, `src/persist/par_nvm.c` depends on a mounted storage backend interface. Whether runtime ID support must also be enabled depends on the selected persisted record layout and backend constraints: the payload-only layouts may be used with `PAR_CFG_ENABLE_ID = 0` only when `PAR_CFG_TABLE_ID_CHECK_EN = 1`, while the stored-ID layouts and the current flash backend path require `PAR_CFG_ENABLE_ID = 1`. Persistence metadata is compiled in automatically under `PAR_CFG_NVM_EN`. The package builds one packaged backend adapter selected from Kconfig. The available packaged paths now include the RT-Thread AT24CXX adapter and the portable flash-ee backend core, which can be bound either to FAL through the repository-root `backend/par_store_backend_flash_ee_fal.c` bridge or to product-specific native flash hooks through the repository-root `backend/par_store_backend_flash_ee_native.c` adapter. +- Live RAM layout and persisted NVM layout are intentionally different. RAM storage is grouped by value width, while the persistence area stores a compile-time ordered slot list using one selected serialized record layout: fixed 4-byte payload slot with size descriptor, fixed 4-byte payload slot without size descriptor, compact natural-width payload with size descriptor, fixed natural-width payload without stored ID, or grouped natural-width payload without stored ID. +- Compile-time persistent order is the primary slot layout contract of the managed NVM image. The stored-ID layouts keep `id` in each record as an integrity and diagnostics field, while the payload-only layouts omit stored `id` and instead rely on compile-time slot order plus table-ID validation. The fixed-slot layouts keep one 4-byte payload slot per persistent parameter, while the compact and payload-only layouts store only the natural 1/2/4-byte payload width. +- The serialized NVM header is written explicitly as a fixed 12-byte storage image (`sign(4) + obj_nb(2) + table_id(4) + crc16(2)`), so on-storage layout does not depend on compiler struct padding. Header CRC-16 covers the serialized `obj_nb + table_id` bytes, while each data record carries its own CRC-8 according to the selected record layout. +- `par_nvm.c` now binds one selected persisted-record layout adapter during initialization. Record address calculation, object preparation, stored-object validation, compatibility policy, and optional write-readback comparison are all delegated through that layout ops table, so the common NVM flow no longer carries layout-specific branching. When `PAR_CFG_NVM_WRITE_VERIFY_EN = 1`, parameter writes and header commits force a backend sync and then perform layout-aware readback verification before the write is considered committed. +- `par_nvm_write(par_num, false)` requests a normal parameter save without an additional caller-forced sync step. It does **not** guarantee RAM-only staging. Backend contracts may still persist data before the call returns, and a successful return means the backend-side persistence work required for that request is complete. +- The flash-emulated EEPROM backend may synchronize one dirty cache window before loading the next window during one logical write or erase. Successful multi-window requests therefore finish fully persisted, but failures are still non-transactional and may leave earlier windows committed while later windows remain old. +- For flash-emulated EEPROM integrations, size `PAR_CFG_NVM_BACKEND_FLASH_EE_CACHE_SIZE` so common parameter objects fit inside one cache window whenever practical. Also keep one must-stay-consistent parameter group within one independently committed window, or add an application-level consistency/version marker if that is not possible. +- Flash-ee build-time configuration now rejects invalid pure-configuration geometry combinations such as logical-size/cache-size values that do not divide by the configured line size or program-size values that do not divide the 64-byte bank header exactly. +- Native flash-ee integrations should provide strong definitions for the required public native hook ABI declared in `parameters/src/persist/backend/par_store_backend_flash_ee.h`; missing required operational or geometry hooks are intended to fail the final link instead of degrading into runtime stub behavior, while benign helper hooks may still use package defaults. +- If the selected storage medium has ECC or other read-health reporting, treat that as a product-level policy input rather than as an automatic parameter-core decision. The business layer should decide whether an ECC event only needs reporting, should trigger parameter reset or rebuild, or should escalate to a wider system fault response. +- CRC calculation is routed through port hooks with bundled software defaults. In this single-target profile the persisted image and the table-ID digest both use the native byte order of the running platform, so no additional byte-order conversion hook is required by the persistence path. +- When `PAR_CFG_TABLE_ID_CHECK_EN = 1`, startup compares the stored table-ID against the live compatibility digest for the stored persistent prefix size from the header (`obj_nb`). Layouts with stored IDs hash external parameter IDs, so prefix ID renumbering still invalidates the image there. `FIXED_PAYLOAD_ONLY` intentionally excludes external parameter IDs and validates only prefix byte-layout compatibility (`obj_nb`, persistent order, and parameter type), which allows pure external-ID renumbering and compatible tail growth without a rebuild there. `GROUPED_PAYLOAD_ONLY` also excludes external parameter IDs, but because regrouped addresses depend on the full live persistent set it rebuilds whenever `stored_count != live_count`. Semantic-only prefix remaps that preserve the same byte layout must still be paired with an explicit `PAR_CFG_TABLE_ID_SCHEMA_VER` bump. Defaults, ranges, names, units, descriptions, and access flags remain outside the digest. +- A table-ID mismatch is treated as an incompatible persisted-layout change, not as a warning-only condition. Startup restores defaults and rebuilds the managed NVM image. Typical triggers are add/remove/reorder/type/ID changes of persistent parameters and transitions between persistent and non-persistent state. When the stored header count is smaller than the compile-time persistent count, layouts with stable prefix addresses repair the image by appending the missing tail slots from current defaults and rewriting the header count; `GROUPED_PAYLOAD_ONLY` instead treats any stored/live count mismatch as incompatible and rebuilds. A stored count larger than the compile-time count is always treated as incompatible and rebuilt. +- `PAR_CFG_TABLE_ID_SCHEMA_VER` defaults in `src/par_cfg.h` and may be overridden in `port/par_cfg_port.h`; the integrator should bump it when intentionally changing the serialized table-ID schema. +- The fixed 4-byte payload slot without size descriptor is not available when a flash backend requires 8-byte aligned writes, because that layout serializes each record to 7 bytes. +- The package intentionally keeps a compile-time ordered slot image instead of introducing a free-layout scanned log. That keeps the address model deterministic and avoids boot-time scan, compaction, and tombstone handling in the common parameter path. +- `par_init()` applies startup default values directly to live storage. Integer default values from `par_table.def` are compiled into a grouped width-based storage object, while `F32` default values are applied to the 32-bit storage group after layout offsets are available only when `PAR_CFG_ENABLE_TYPE_F32 = 1`. Because this startup initialization does not go through the public setter path, it does not invoke runtime validation or on-change callbacks. +- `PAR_CFG_ENABLE_RESET_ALL_RAW` controls whether raw reset-all support and grouped default mirror snapshot support are enabled. - // Prevent parameter to take exact -10 value - if ( val.i8 == -10 ) return false; - else return true; -} +## Related projects -// Define parameter validation for "ePAR_CH1_REF_VAL" parameter -PAR_DEFINE_VALIDATION( par_validate, ePAR_CH1_REF_VAL, validate_par_value); +- [CLI module](https://github.com/GeneralEmbeddedCLibraries/cli) +- [NVM module](https://github.com/GeneralEmbeddedCLibraries/nvm) +- [General Embedded C Library Manual](https://github.com/GeneralEmbeddedCLibraries/documentation/blob/develop/General_Embedded_C_Library_Manual.pdf) -// Register at init phase -@init -{ - if ( ePAR_OK != par_register_validation( &par_validate )) - { - // Operation error... - // Further actions here... - } -} -``` +## Flash-emulated EEPROM backend +See `parameters/docs/flash-ee-backend-design.md` for the portable core, configuration header, commit/recovery model, cross-window write semantics, and FAL/native integration guidance. diff --git a/Third_Party/autogen_parameter_manager/backend/par_store_backend_flash_ee_fal.c b/Third_Party/autogen_parameter_manager/backend/par_store_backend_flash_ee_fal.c new file mode 100644 index 0000000..1511a37 --- /dev/null +++ b/Third_Party/autogen_parameter_manager/backend/par_store_backend_flash_ee_fal.c @@ -0,0 +1,276 @@ +/** + * @file par_store_backend_flash_ee_fal.c + * @brief Bind the generic flash-emulated EEPROM core to a FAL partition. + * @author wdfk-prog + * @version 1.0 + * @date 2026-04-14 + * + * @copyright Copyright (c) 2026 Ziga Miklosic. Distributed under the MIT license. + * + * @note : + * @par Change Log: + * Date Version Author Description + * 2026-04-14 1.0 wdfk-prog first version + */ +#include "../parameters/src/par_cfg.h" +#include "../parameters/src/persist/backend/par_store_backend_flash_ee.h" + +#if (1 == PAR_CFG_NVM_EN) && (1 == PAR_CFG_NVM_BACKEND_FLASH_EE_EN) && (1 == PAR_CFG_NVM_BACKEND_FLASH_EE_PORT_FAL_EN) + +#include + +#include + +/** + * @brief Hold the runtime context for the FAL flash-ee adapter. + */ +typedef struct +{ + const struct fal_partition *p_part; /**< Bound FAL partition handle. */ + const struct fal_flash_dev *p_flash; /**< Underlying flash device for the bound partition. */ + const char *partition_name; /**< Configured FAL partition name. */ + bool is_init; /**< True after the adapter resolves the partition. */ +} par_store_flash_ee_fal_ctx_t; + +/** + * @brief Store the singleton FAL adapter context. + */ +static par_store_flash_ee_fal_ctx_t g_par_store_flash_ee_fal_ctx = { + .p_part = NULL, + .p_flash = NULL, + .partition_name = PAR_CFG_NVM_BACKEND_FLASH_EE_FAL_PARTITION_NAME, + .is_init = false, +}; + +/** + * @brief Initialize the FAL-backed flash port. + * + * @param[in,out] p_ctx Mutable FAL port context. + * @return ePAR_OK on success, otherwise an error code. + */ +static par_status_t par_store_flash_ee_fal_init(void *p_ctx) +{ + par_store_flash_ee_fal_ctx_t *p_fal_ctx = (par_store_flash_ee_fal_ctx_t *)p_ctx; + + if (NULL == p_fal_ctx) + { + return ePAR_ERROR_PARAM; + } + + if (true == p_fal_ctx->is_init) + { + return ePAR_OK; + } + + p_fal_ctx->p_part = fal_partition_find(p_fal_ctx->partition_name); + if (NULL == p_fal_ctx->p_part) + { + PAR_ERR_PRINT("PAR_FLASH_EE_FAL: partition not found, name=%s", p_fal_ctx->partition_name); + return ePAR_ERROR_INIT; + } + + p_fal_ctx->p_flash = fal_flash_device_find(p_fal_ctx->p_part->flash_name); + if (NULL == p_fal_ctx->p_flash) + { + PAR_ERR_PRINT("PAR_FLASH_EE_FAL: flash device not found, partition=%s flash=%s", + p_fal_ctx->partition_name, + p_fal_ctx->p_part->flash_name); + p_fal_ctx->p_part = NULL; + return ePAR_ERROR_INIT; + } + + p_fal_ctx->is_init = true; + return ePAR_OK; +} + +/** + * @brief Deinitialize the FAL-backed flash port. + * + * @param[in,out] p_ctx Mutable FAL port context. + * @return ePAR_OK on success. + */ +static par_status_t par_store_flash_ee_fal_deinit(void *p_ctx) +{ + par_store_flash_ee_fal_ctx_t *p_fal_ctx = (par_store_flash_ee_fal_ctx_t *)p_ctx; + + if (NULL == p_fal_ctx) + { + return ePAR_ERROR_PARAM; + } + + p_fal_ctx->p_part = NULL; + p_fal_ctx->p_flash = NULL; + p_fal_ctx->is_init = false; + return ePAR_OK; +} + +/** + * @brief Report whether the FAL-backed flash port is initialized. + * + * @param[in] p_ctx Immutable FAL port context. + * @param[out] p_is_init Receives the initialization state. + * @return ePAR_OK on success, otherwise an error code. + */ +static par_status_t par_store_flash_ee_fal_is_init(const void *p_ctx, bool *p_is_init) +{ + const par_store_flash_ee_fal_ctx_t *p_fal_ctx = (const par_store_flash_ee_fal_ctx_t *)p_ctx; + + if ((NULL == p_fal_ctx) || (NULL == p_is_init)) + { + return ePAR_ERROR_PARAM; + } + + *p_is_init = p_fal_ctx->is_init; + return ePAR_OK; +} + +/** + * @brief Read raw bytes from the FAL partition. + * + * @param[in] p_ctx Immutable FAL port context. + * @param[in] addr Byte offset inside the bound partition. + * @param[in] size Number of bytes to read. + * @param[out] p_buf Destination buffer. + * @return ePAR_OK on success, otherwise an error code. + */ +static par_status_t par_store_flash_ee_fal_read(const void *p_ctx, uint32_t addr, uint32_t size, uint8_t *p_buf) +{ + const par_store_flash_ee_fal_ctx_t *p_fal_ctx = (const par_store_flash_ee_fal_ctx_t *)p_ctx; + + if ((NULL == p_fal_ctx) || (NULL == p_fal_ctx->p_part) || (NULL == p_buf)) + { + return ePAR_ERROR_PARAM; + } + + return ((int)size == fal_partition_read(p_fal_ctx->p_part, (long)addr, p_buf, (size_t)size)) ? ePAR_OK : ePAR_ERROR_NVM; +} + +/** + * @brief Program raw bytes into the FAL partition. + * + * @param[in] p_ctx Immutable FAL port context. + * @param[in] addr Byte offset inside the bound partition. + * @param[in] size Number of bytes to program. + * @param[in] p_buf Source buffer. + * @return ePAR_OK on success, otherwise an error code. + */ +static par_status_t par_store_flash_ee_fal_program(const void *p_ctx, uint32_t addr, uint32_t size, const uint8_t *p_buf) +{ + const par_store_flash_ee_fal_ctx_t *p_fal_ctx = (const par_store_flash_ee_fal_ctx_t *)p_ctx; + + if ((NULL == p_fal_ctx) || (NULL == p_fal_ctx->p_part) || (NULL == p_buf)) + { + return ePAR_ERROR_PARAM; + } + + return ((int)size == fal_partition_write(p_fal_ctx->p_part, (long)addr, p_buf, (size_t)size)) ? ePAR_OK : ePAR_ERROR_NVM; +} + +/** + * @brief Erase bytes inside the FAL partition. + * + * @param[in] p_ctx Immutable FAL port context. + * @param[in] addr Byte offset inside the bound partition. + * @param[in] size Number of bytes to erase. + * @return ePAR_OK on success, otherwise an error code. + */ +static par_status_t par_store_flash_ee_fal_erase(const void *p_ctx, uint32_t addr, uint32_t size) +{ + const par_store_flash_ee_fal_ctx_t *p_fal_ctx = (const par_store_flash_ee_fal_ctx_t *)p_ctx; + + if ((NULL == p_fal_ctx) || (NULL == p_fal_ctx->p_part)) + { + return ePAR_ERROR_PARAM; + } + + return ((int)size == fal_partition_erase(p_fal_ctx->p_part, (long)addr, (size_t)size)) ? ePAR_OK : ePAR_ERROR_NVM; +} + +/** + * @brief Return the total FAL partition size. + * + * @param[in] p_ctx Immutable FAL port context. + * @return Partition size in bytes. + */ +static uint32_t par_store_flash_ee_fal_get_region_size(const void *p_ctx) +{ + const par_store_flash_ee_fal_ctx_t *p_fal_ctx = (const par_store_flash_ee_fal_ctx_t *)p_ctx; + return ((NULL != p_fal_ctx) && (NULL != p_fal_ctx->p_part)) ? (uint32_t)p_fal_ctx->p_part->len : 0u; +} + +/** + * @brief Return the FAL partition erase size. + * + * @param[in] p_ctx Immutable FAL port context. + * @return Erase size in bytes. + */ +static uint32_t par_store_flash_ee_fal_get_erase_size(const void *p_ctx) +{ + const par_store_flash_ee_fal_ctx_t *p_fal_ctx = (const par_store_flash_ee_fal_ctx_t *)p_ctx; + return ((NULL != p_fal_ctx) && (NULL != p_fal_ctx->p_flash)) ? (uint32_t)p_fal_ctx->p_flash->blk_size : 0u; +} + +/** + * @brief Return the FAL program size. + * + * @param[in] p_ctx Immutable FAL port context. + * @return Program size in bytes. + * + * @note FAL does not expose one universal program granularity field. The + * backend therefore uses the package configuration value. + */ +static uint32_t par_store_flash_ee_fal_get_program_size(const void *p_ctx) +{ + (void)p_ctx; + return PAR_CFG_NVM_BACKEND_FLASH_EE_PROGRAM_SIZE; +} + +/** + * @brief Return the port name used by the FAL adapter. + * + * @param[in] p_ctx Immutable FAL port context. + * @return Constant name string. + */ +static const char *par_store_flash_ee_fal_get_name(const void *p_ctx) +{ + const par_store_flash_ee_fal_ctx_t *p_fal_ctx = (const par_store_flash_ee_fal_ctx_t *)p_ctx; + return ((NULL != p_fal_ctx) && (NULL != p_fal_ctx->partition_name)) ? p_fal_ctx->partition_name : "fal"; +} + +/** + * @brief Expose the FAL-backed flash port operations to the generic core. + */ +static const par_store_flash_ee_port_api_t g_par_store_flash_ee_fal_port = { + .init = par_store_flash_ee_fal_init, + .deinit = par_store_flash_ee_fal_deinit, + .is_init = par_store_flash_ee_fal_is_init, + .read = par_store_flash_ee_fal_read, + .program = par_store_flash_ee_fal_program, + .erase = par_store_flash_ee_fal_erase, + .get_region_size = par_store_flash_ee_fal_get_region_size, + .get_erase_size = par_store_flash_ee_fal_get_erase_size, + .get_program_size = par_store_flash_ee_fal_get_program_size, + .get_name = par_store_flash_ee_fal_get_name, +}; + +/** + * @brief Bind the FAL adapter to the generic flash-ee backend core. + * + * @return ePAR_OK on success, otherwise an error code from the generic core. + */ +par_status_t par_store_backend_bind(void) +{ + return par_store_backend_flash_ee_bind_port(&g_par_store_flash_ee_fal_port, &g_par_store_flash_ee_fal_ctx); +} + +/** + * @brief Return the generic flash-ee backend API after adapter binding. + * + * @return Generic flash-ee backend API table. + */ +const par_store_backend_api_t * par_store_backend_get_api(void) +{ + return par_store_backend_flash_ee_get_api(); +} + +#endif /* flash-ee + fal */ diff --git a/Third_Party/autogen_parameter_manager/backend/par_store_backend_flash_ee_native.c b/Third_Party/autogen_parameter_manager/backend/par_store_backend_flash_ee_native.c new file mode 100644 index 0000000..90ba141 --- /dev/null +++ b/Third_Party/autogen_parameter_manager/backend/par_store_backend_flash_ee_native.c @@ -0,0 +1,264 @@ +/** + * @file par_store_backend_flash_ee_native.c + * @brief Bind the generic flash-emulated EEPROM core to native flash hooks. + * @author wdfk-prog + * @version 1.0 + * @date 2026-04-14 + * + * @copyright Copyright (c) 2026 Ziga Miklosic. Distributed under the MIT license. + * + * @note : + * @par Change Log: + * Date Version Author Description + * 2026-04-14 1.0 wdfk-prog first version + */ +#include "../parameters/src/par_cfg.h" +#include "../parameters/src/persist/backend/par_store_backend_flash_ee.h" + +#if (1 == PAR_CFG_NVM_EN) && (1 == PAR_CFG_NVM_BACKEND_FLASH_EE_EN) && (1 == PAR_CFG_NVM_BACKEND_FLASH_EE_PORT_NATIVE_EN) + +/** + * @brief Hold runtime geometry and state for the native flash-ee adapter. + */ +typedef struct +{ + bool is_init; /**< True after the native hooks finish initialization. */ + uint32_t region_size; /**< Total persistence-region size reported by the hooks. */ + uint32_t erase_size; /**< Physical erase granularity reported by the hooks. */ + uint32_t program_size; /**< Physical program granularity reported by the hooks. */ + const char *name; /**< Diagnostic port name reported by the hooks. */ +} par_store_flash_ee_native_ctx_t; + +/** + * @brief Weak default native-port deinitializer. + * + * @return ePAR_OK because the default implementation owns no resources. + */ +PAR_PORT_WEAK par_status_t par_store_flash_ee_native_port_deinit(void) +{ + return ePAR_OK; +} + +/** + * @brief Weak default native-port name hook. + * + * @return Default diagnostic name for the native adapter. + */ +PAR_PORT_WEAK const char *par_store_flash_ee_native_port_name(void) +{ + return "native"; +} + +/** + * @brief Store the singleton native-adapter context. + */ +static par_store_flash_ee_native_ctx_t g_par_store_flash_ee_native_ctx = { + .is_init = false, + .region_size = 0u, + .erase_size = 0u, + .program_size = 0u, + .name = "native", +}; + +/** + * @brief Initialize the native-flash port. + * + * @param[in,out] p_ctx Mutable native-port context. + * @return ePAR_OK on success, otherwise an error code. + */ +static par_status_t par_store_flash_ee_native_init(void *p_ctx) +{ + par_store_flash_ee_native_ctx_t *p_native_ctx = (par_store_flash_ee_native_ctx_t *)p_ctx; + par_status_t status = ePAR_OK; + + if (NULL == p_native_ctx) + { + return ePAR_ERROR_PARAM; + } + + if (true == p_native_ctx->is_init) + { + return ePAR_OK; + } + + status = par_store_flash_ee_native_port_init(); + if (ePAR_OK == status) + { + p_native_ctx->region_size = par_store_flash_ee_native_port_region_size(); + p_native_ctx->erase_size = par_store_flash_ee_native_port_erase_size(); + p_native_ctx->program_size = par_store_flash_ee_native_port_program_size(); + p_native_ctx->name = par_store_flash_ee_native_port_name(); + p_native_ctx->is_init = true; + } + + return status; +} + +/** + * @brief Deinitialize the native-flash port. + * + * @param[in,out] p_ctx Mutable native-port context. + * @return ePAR_OK on success, otherwise an error code. + */ +static par_status_t par_store_flash_ee_native_deinit(void *p_ctx) +{ + par_store_flash_ee_native_ctx_t *p_native_ctx = (par_store_flash_ee_native_ctx_t *)p_ctx; + + if (NULL == p_native_ctx) + { + return ePAR_ERROR_PARAM; + } + + p_native_ctx->is_init = false; + return par_store_flash_ee_native_port_deinit(); +} + +/** + * @brief Report whether the native-flash port is initialized. + * + * @param[in] p_ctx Immutable native-port context. + * @param[out] p_is_init Receives the initialization state. + * @return ePAR_OK on success, otherwise an error code. + */ +static par_status_t par_store_flash_ee_native_is_init(const void *p_ctx, bool *p_is_init) +{ + const par_store_flash_ee_native_ctx_t *p_native_ctx = (const par_store_flash_ee_native_ctx_t *)p_ctx; + + if ((NULL == p_native_ctx) || (NULL == p_is_init)) + { + return ePAR_ERROR_PARAM; + } + + *p_is_init = p_native_ctx->is_init; + return ePAR_OK; +} + +/** + * @brief Read raw bytes through the native-flash hooks. + * + * @param[in] p_ctx Immutable native-port context. + * @param[in] addr Byte offset inside the bound region. + * @param[in] size Number of bytes to read. + * @param[out] p_buf Destination buffer. + * @return ePAR_OK on success, otherwise an error code. + */ +static par_status_t par_store_flash_ee_native_read(const void *p_ctx, uint32_t addr, uint32_t size, uint8_t *p_buf) +{ + (void)p_ctx; + return par_store_flash_ee_native_port_read(addr, size, p_buf); +} + +/** + * @brief Program raw bytes through the native-flash hooks. + * + * @param[in] p_ctx Immutable native-port context. + * @param[in] addr Byte offset inside the bound region. + * @param[in] size Number of bytes to program. + * @param[in] p_buf Source buffer. + * @return ePAR_OK on success, otherwise an error code. + */ +static par_status_t par_store_flash_ee_native_program(const void *p_ctx, uint32_t addr, uint32_t size, const uint8_t *p_buf) +{ + (void)p_ctx; + return par_store_flash_ee_native_port_program(addr, size, p_buf); +} + +/** + * @brief Erase bytes through the native-flash hooks. + * + * @param[in] p_ctx Immutable native-port context. + * @param[in] addr Byte offset inside the bound region. + * @param[in] size Number of bytes to erase. + * @return ePAR_OK on success, otherwise an error code. + */ +static par_status_t par_store_flash_ee_native_erase(const void *p_ctx, uint32_t addr, uint32_t size) +{ + (void)p_ctx; + return par_store_flash_ee_native_port_erase(addr, size); +} + +/** + * @brief Return the native-flash region size. + * + * @param[in] p_ctx Immutable native-port context. + * @return Region size in bytes. + */ +static uint32_t par_store_flash_ee_native_get_region_size(const void *p_ctx) +{ + const par_store_flash_ee_native_ctx_t *p_native_ctx = (const par_store_flash_ee_native_ctx_t *)p_ctx; + return (NULL != p_native_ctx) ? p_native_ctx->region_size : 0u; +} + +/** + * @brief Return the native-flash erase size. + * + * @param[in] p_ctx Immutable native-port context. + * @return Erase size in bytes. + */ +static uint32_t par_store_flash_ee_native_get_erase_size(const void *p_ctx) +{ + const par_store_flash_ee_native_ctx_t *p_native_ctx = (const par_store_flash_ee_native_ctx_t *)p_ctx; + return (NULL != p_native_ctx) ? p_native_ctx->erase_size : 0u; +} + +/** + * @brief Return the native-flash program size. + * + * @param[in] p_ctx Immutable native-port context. + * @return Program size in bytes. + */ +static uint32_t par_store_flash_ee_native_get_program_size(const void *p_ctx) +{ + const par_store_flash_ee_native_ctx_t *p_native_ctx = (const par_store_flash_ee_native_ctx_t *)p_ctx; + return (NULL != p_native_ctx) ? p_native_ctx->program_size : 0u; +} + +/** + * @brief Return the native-flash port name. + * + * @param[in] p_ctx Immutable native-port context. + * @return Constant name string. + */ +static const char *par_store_flash_ee_native_get_name(const void *p_ctx) +{ + const par_store_flash_ee_native_ctx_t *p_native_ctx = (const par_store_flash_ee_native_ctx_t *)p_ctx; + return ((NULL != p_native_ctx) && (NULL != p_native_ctx->name)) ? p_native_ctx->name : "native"; +} + +/** + * @brief Expose the native flash port operations to the generic core. + */ +static const par_store_flash_ee_port_api_t g_par_store_flash_ee_native_port = { + .init = par_store_flash_ee_native_init, + .deinit = par_store_flash_ee_native_deinit, + .is_init = par_store_flash_ee_native_is_init, + .read = par_store_flash_ee_native_read, + .program = par_store_flash_ee_native_program, + .erase = par_store_flash_ee_native_erase, + .get_region_size = par_store_flash_ee_native_get_region_size, + .get_erase_size = par_store_flash_ee_native_get_erase_size, + .get_program_size = par_store_flash_ee_native_get_program_size, + .get_name = par_store_flash_ee_native_get_name, +}; + +/** + * @brief Bind the native adapter to the generic flash-ee backend core. + * + * @return ePAR_OK on success, otherwise an error code from the generic core. + */ +par_status_t par_store_backend_bind(void) +{ + return par_store_backend_flash_ee_bind_port(&g_par_store_flash_ee_native_port, &g_par_store_flash_ee_native_ctx); +} + +/** + * @brief Return the generic flash-ee backend API after adapter binding. + * + * @return Generic flash-ee backend API table. + */ +const par_store_backend_api_t * par_store_backend_get_api(void) +{ + return par_store_backend_flash_ee_get_api(); +} + +#endif /* flash-ee + native */ diff --git a/doc/DeviceParameter_VerificationReport.xlsx b/docs/DeviceParameter_VerificationReport.xlsx similarity index 100% rename from doc/DeviceParameter_VerificationReport.xlsx rename to docs/DeviceParameter_VerificationReport.xlsx diff --git a/docs/api-reference.md b/docs/api-reference.md new file mode 100644 index 0000000..36b3b66 --- /dev/null +++ b/docs/api-reference.md @@ -0,0 +1,307 @@ +# API reference + +This document groups the public API from `src/par.h` by responsibility. + +## Conventions + +- Most runtime APIs require `par_init()` to be called first. +- Some APIs are compiled only when the matching configuration option is enabled. +- `par_num_t` is the internal parameter index. +- ID-based APIs depend on `PAR_CFG_ENABLE_ID = 1`. +- NVM APIs depend on `PAR_CFG_NVM_EN = 1`. +- `F32` typed APIs depend on `PAR_CFG_ENABLE_TYPE_F32 = 1`. +- Validation registration APIs depend on `PAR_CFG_ENABLE_RUNTIME_VALIDATION = 1`. +- On-change registration APIs depend on `PAR_CFG_ENABLE_CHANGE_CALLBACK = 1`. +- Logging output depends on `PAR_CFG_DEBUG_EN = 1`. When the RT-Thread package port is used, log output is split into `INFO` / `DEBUG` / `WARN` / `ERROR` levels. + +## Compile-time availability notes + +The module conditionally compiles parts of the API based on configuration. + +- `PAR_CFG_NVM_EN = 1` enables NVM APIs +- `PAR_CFG_ENABLE_ID = 1` enables ID-dependent behavior +- `PAR_CFG_ENABLE_TYPE_F32 = 1` enables: + - `par_set_f32()` + - `par_get_f32()` + - `par_set_f32_fast()` + - `PAR_SET_F32` +- `PAR_CFG_ENABLE_RUNTIME_VALIDATION = 1` enables: + - `par_register_validation()` + - runtime validation callbacks in normal setter paths +- `PAR_CFG_ENABLE_CHANGE_CALLBACK = 1` enables: + - `par_register_on_change_cb()` + - on-change callbacks in normal setter paths +- `PAR_CFG_ENABLE_RESET_ALL_RAW = 1` enables: + - `par_reset_all_to_default_raw()` + + +## Status notes + +- `ePAR_ERROR_ACCESS` indicates that a checked public value API rejected the requested access. Checked setters reject targets without `ePAR_ACCESS_WRITE`; checked getters reject targets without `ePAR_ACCESS_READ`. +- Warning bit values were shifted to make room for the new access-denied error bit. + +## Lifecycle + +| Function | Description | +| --- | --- | +| `par_init()` | Initialize the module, validate the table, bind layout/runtime state, optionally run configured runtime ID diagnostics, apply default values to live storage, and optionally load persisted values from NVM. Startup defaults are applied internally and do not use the public setter path. | +| `par_deinit()` | Deinitialize the module, including interface-layer resources. When NVM support is enabled, it only deinitializes the underlying NVM module if this module initialized it. The top-level init state is cleared only when child deinit steps succeed; if NVM backend deinit fails, the module remains initialized. | +| `par_is_init()` | Return whether the module is initialized. | + +## Mutex helpers + +| Function | Description | +| --- | --- | +| `par_acquire_mutex(par_num)` | Acquire the parameter lock for a specific parameter path. | +| `par_release_mutex(par_num)` | Release the parameter lock. | + +These are relevant only when mutex support is enabled in the integration. + +## Pointer-based setters + +| Function | Description | +| --- | --- | +| `par_set(par_num, p_val)` | Set a parameter from a typed pointer. This checked public write path returns `ePAR_ERROR_ACCESS` when `PAR_CFG_ENABLE_ACCESS = 1` and the target lacks `ePAR_ACCESS_WRITE`. | +| `par_set_fast(par_num, p_val)` | Set a parameter from a typed pointer through the unchecked fast path. This API resolves the runtime type and then dispatches to the matching `par_set_xxx_fast()` implementation. | +| `par_set_by_id(id, p_val)` | Set a parameter using its external ID. This path resolves the ID to `par_num_t` and then uses the same checked setter flow as `par_set()`. | + +## Typed setter macro wrappers + +| Macro | Description | +| --- | --- | +| `PAR_SET_U8(par_num, value)` | Call `par_set_u8()` through a typed macro wrapper. | +| `PAR_SET_I8(par_num, value)` | Call `par_set_i8()` through a typed macro wrapper. | +| `PAR_SET_U16(par_num, value)` | Call `par_set_u16()` through a typed macro wrapper. | +| `PAR_SET_I16(par_num, value)` | Call `par_set_i16()` through a typed macro wrapper. | +| `PAR_SET_U32(par_num, value)` | Call `par_set_u32()` through a typed macro wrapper. | +| `PAR_SET_I32(par_num, value)` | Call `par_set_i32()` through a typed macro wrapper. | +| `PAR_SET_F32(par_num, value)` | Call `par_set_f32()` through a typed macro wrapper. Available only when `PAR_CFG_ENABLE_TYPE_F32 = 1`. | + +## Typed setter functions + +| Function | Description | +| --- | --- | +| `par_set_u8()` | Set a `U8` parameter. | +| `par_set_i8()` | Set an `I8` parameter. | +| `par_set_u16()` | Set a `U16` parameter. | +| `par_set_i16()` | Set an `I16` parameter. | +| `par_set_u32()` | Set a `U32` parameter. | +| `par_set_i32()` | Set an `I32` parameter. | +| `par_set_f32()` | Set an `F32` parameter. Available only when `PAR_CFG_ENABLE_TYPE_F32 = 1`. | + +Normal typed setters are the canonical checked setter path. When `PAR_CFG_ENABLE_ACCESS = 1`, they enforce external write capability and return `ePAR_ERROR_ACCESS` when the target does not expose `ePAR_ACCESS_WRITE`. They may also include runtime validation callbacks and on-change callbacks when the matching configuration options are enabled. + +## Fast setters + +| Function | Description | +| --- | --- | +| `par_set_u8_fast()` | Fast set for `U8`. | +| `par_set_i8_fast()` | Fast set for `I8`. | +| `par_set_u16_fast()` | Fast set for `U16`. | +| `par_set_i16_fast()` | Fast set for `I16`. | +| `par_set_u32_fast()` | Fast set for `U32`. | +| `par_set_i32_fast()` | Fast set for `I32`. | +| `par_set_f32_fast()` | Fast set for `F32`. Available only when `PAR_CFG_ENABLE_TYPE_F32 = 1`. | + +Use these only in controlled hot paths. Fast setters intentionally bypass access enforcement, runtime validation callbacks, and on-change callbacks. + +## Fast bitwise update helpers + +These helpers are available for unsigned integer widths and are intended only for controlled, high-frequency updates of flags/bitmask parameters. Treat them as unchecked fast paths, not as general-purpose setters for ranged numeric values. + +| Function | Description | +| --- | --- | +| `par_bitand_set_u8_fast()` | Fast bitwise AND update for `U8`. | +| `par_bitand_set_u16_fast()` | Fast bitwise AND update for `U16`. | +| `par_bitand_set_u32_fast()` | Fast bitwise AND update for `U32`. | +| `par_bitor_set_u8_fast()` | Fast bitwise OR update for `U8`. | +| `par_bitor_set_u16_fast()` | Fast bitwise OR update for `U16`. | +| `par_bitor_set_u32_fast()` | Fast bitwise OR update for `U32`. | + +## Reset and change tracking + +| Function | Description | +| --- | --- | +| `par_set_to_default(par_num)` | Reset one parameter to its configured default value through the internal fast typed setter path. It bypasses access enforcement, validation callbacks, and on-change callbacks. | +| `par_set_all_to_default()` | Reset all parameters to their default values. When `PAR_CFG_ENABLE_RESET_ALL_RAW = 1`, this public API forwards to the raw grouped-storage reset path for speed. Otherwise it iterates through `par_set_to_default()` and aggregates per-parameter status bits. | +| `par_reset_all_to_default_raw()` | Restore all live values from a grouped default mirror snapshot via raw memory copy. The internal storage model still uses `U8/U16/U32` width groups. Available only when `PAR_CFG_ENABLE_RESET_ALL_RAW = 1`. | +| `par_has_changed(par_num, p_has_changed)` | Report whether the value differs from its default. | + +`par_set_to_default()` always uses the internal fast typed setter path. + +`par_set_all_to_default()` is configuration-dependent. When `PAR_CFG_ENABLE_RESET_ALL_RAW = 1`, it forwards to `par_reset_all_to_default_raw()` for the fastest bulk restore path. When the raw-reset option is disabled, it iterates through parameters and uses the same fast default-restore path as `par_set_to_default()`. + +Use `par_reset_all_to_default_raw()` when you want to call the raw grouped-storage restore path explicitly. + +These reset APIs are different from startup initialization: + +- `par_init()` applies startup defaults internally to live storage +- `par_set_to_default()` uses fast default-restore semantics for one parameter +- `par_set_all_to_default()` uses raw restore semantics when raw reset is enabled, otherwise it uses the same fast default-restore semantics as `par_set_to_default()` +- `par_reset_all_to_default_raw()` always bypasses per-parameter runtime setter semantics + +## Pointer-based getters + +| Function | Description | +| --- | --- | +| `par_get(par_num, p_val)` | Read a parameter into a typed destination pointer. This checked public read path returns `ePAR_ERROR_ACCESS` when `PAR_CFG_ENABLE_ACCESS = 1` and the target lacks `ePAR_ACCESS_READ`. | +| `par_get_by_id(id, p_val)` | Read a parameter using its external ID. After ID resolution, this uses the same checked public read flow as `par_get()`. | + +Typed getter macros are removed. Call the typed getter functions directly and always check the returned status. + +## Typed getter functions + +| Function | Description | +| --- | --- | +| `par_get_u8(par_num, p_val)` | Read a `U8` parameter into `*p_val`. Returns status. | +| `par_get_i8(par_num, p_val)` | Read an `I8` parameter into `*p_val`. Returns status. | +| `par_get_u16(par_num, p_val)` | Read a `U16` parameter into `*p_val`. Returns status. | +| `par_get_i16(par_num, p_val)` | Read an `I16` parameter into `*p_val`. Returns status. | +| `par_get_u32(par_num, p_val)` | Read a `U32` parameter into `*p_val`. Returns status. | +| `par_get_i32(par_num, p_val)` | Read an `I32` parameter into `*p_val`. Returns status. | +| `par_get_f32(par_num, p_val)` | Read an `F32` parameter into `*p_val`. Available only when `PAR_CFG_ENABLE_TYPE_F32 = 1`. Returns status. | +| `par_get_default(par_num, p_val)` | Read the configured default value for a parameter from the metadata table. This API does not consume runtime read capability metadata. | + +Checked typed getters are the canonical public read path. When `PAR_CFG_ENABLE_ACCESS = 1`, they enforce external read capability and return `ePAR_ERROR_ACCESS` when the target does not expose `ePAR_ACCESS_READ`. + +## Metadata access + +These APIs do not follow the same runtime usage pattern as the value access APIs. They expose parameter metadata from the configuration table. + +| Function | Description | +| --- | --- | +| `par_get_config(par_num)` | Return the full configuration object for one parameter. | +| `par_get_name(par_num)` | Return the display name when name metadata is enabled. | +| `par_get_range(par_num)` | Return the configured min/max range when range metadata is enabled. | +| `par_get_unit(par_num)` | Return the engineering unit when unit metadata is enabled. | +| `par_get_desc(par_num)` | Return the description string when description metadata is enabled. | +| `par_get_type(par_num)` | Return the parameter type enum. | +| `par_get_access(par_num)` | Return the external access capability bit mask when enabled. Checked public value getters use its read bit; checked public setters use its write bit. | +| `par_get_read_roles(par_num)` | Return the configured read-role bit set when role policy metadata is enabled. | +| `par_get_write_roles(par_num)` | Return the configured write-role bit set when role policy metadata is enabled. | +| `par_can_read(par_num, roles)` | Test the supplied caller role bits (`par_role_t`) against the parameter read-role metadata. When access metadata is enabled, read capability must also be present. | +| `par_can_write(par_num, roles)` | Test the supplied caller role bits (`par_role_t`) against the parameter write-role metadata. When access metadata is enabled, write capability must also be present. | +| `par_is_persistent(par_num)` | Return whether the parameter is marked persistent when enabled. | +| `par_get_num_by_id(id, p_par_num)` | Convert an external ID to `par_num_t` through the compile-time generated static ID map. This metadata API does not require `par_init()`. | +| `par_get_id_by_num(par_num, p_id)` | Convert `par_num_t` to external ID. | + +The role-policy option does not own a global login/session state inside the core. Integrators are expected to supply the current caller role bits (`par_role_t`) from their own CLI, service, or transport context when calling `par_can_read()` / `par_can_write()`. Core value getters/setters do not implicitly consume role masks unless an integration layer chooses to wrap those APIs with role-aware policy checks. + +## NVM APIs + +Available only when `PAR_CFG_NVM_EN = 1` and a concrete parameter-storage backend is linked. + +The managed NVM payload area is a compile-time ordered slot list, not a width-group partition like the live RAM layout. Each slot uses the selected serialized record layout: fixed 4-byte payload slot with size descriptor, fixed 4-byte payload slot without size descriptor, compact natural-width payload with size descriptor, fixed natural-width payload without stored ID, or grouped natural-width payload without stored ID. Slot order is derived directly from the compile-time persistent order in `par_table.def`, so the loader restores slot `i` to compile-time persistent slot `i`. If the stored count is larger than the current compile-time persistent count, startup treats the image as incompatible and the managed NVM area is rebuilt from live defaults. If the stored count is smaller, layouts with stable prefix addresses restore the stored prefix and append the missing tail slots from current defaults before the header count is rewritten; `GROUPED_PAYLOAD_ONLY` instead rebuilds on any stored/live count mismatch. Self-describing layouts keep `id` inside each object as a validation and diagnostics field, while the payload-only layouts resolve records entirely from the compile-time slot map. + +The serialized NVM header is emitted explicitly as a fixed 12-byte image (`sign + obj_nb + table_id + crc16`) rather than by writing raw struct memory. Header CRC-16 covers the serialized native-order `obj_nb + table_id` bytes, so corrupted header bytes fail header validation before any compatibility decision is made. Each small data record uses CRC-8 according to the selected record layout. When `PAR_CFG_TABLE_ID_CHECK_EN = 1`, `par_nvm_init()` compares the stored table-ID against the live compatibility digest for the stored persistent prefix length from the header. Layouts with stored IDs hash parameter IDs inside that prefix. Payload-only layouts hash only the stored prefix byte layout (`obj_nb`, persistent order, and parameter type) while intentionally excluding external parameter IDs. Startup first validates header CRC/signature, then validates compatibility, then loads payload objects only if both checks pass. If the stored count is smaller than the compile-time persistent count, layouts with stable prefix addresses restore the stored prefix, append the missing tail slots from live defaults, and rewrite the header count. `GROUPED_PAYLOAD_ONLY` is excluded from that repair path and rebuilds on any stored/live count mismatch. If the stored count is larger than the compile-time persistent count, the image is treated as incompatible and rebuilt. The collected error bits then drive a centralized recovery flow: NVM access errors restore live RAM values to defaults, while table-ID/header/CRC mismatches restore defaults and rebuild the managed NVM image. Compatible tail growth is therefore allowed for layouts with stable prefix addresses, while prefix add/remove/reorder/type changes still rebuild; layouts with stored IDs additionally rebuild on prefix ID changes, `FIXED_PAYLOAD_ONLY` additionally allows pure external-ID renumbering, and `GROUPED_PAYLOAD_ONLY` rebuilds whenever the stored/live counts differ. When a payload-only prefix is semantically remapped without changing its byte layout, the integrator must bump `PAR_CFG_TABLE_ID_SCHEMA_VER` explicitly. CRC calculation is exposed through port hooks with bundled software defaults. +The runtime NVM core binds one compile-time selected layout ops table during initialization. That table owns address calculation, persisted object preparation, stored-object validation, compatibility policy, and optional readback-compare behavior. When `PAR_CFG_NVM_WRITE_VERIFY_EN = 1`, record writes and header writes both force a backend sync and then run layout-aware readback verification before the write is treated as committed. + +If the selected storage backend or storage medium exposes ECC status, the parameter core still does not hard-code the recovery policy. Whether an ECC event should only be reported, should trigger parameter reset/rebuild, or should escalate into a wider system fault response must be decided by the business layer. + +### `par_nvm_write(..., nvm_sync)` semantics + +Treat `nvm_sync` as a request for an additional explicit backend sync step, not as a universal promise that `false` means RAM-only staging. + +- `nvm_sync = false` means the common layer does not request an extra sync after the write path. Backend-specific contracts may still persist data before the call returns. +- `nvm_sync = true` asks the common layer to issue an explicit backend sync after the write path, in addition to any backend-internal persistence already required by that backend contract. +- A successful `par_nvm_write()` return means the backend-side persistence work required for that request has completed. +- This API does not provide transactional atomicity across backend-specific internal chunking. + +For the flash-emulated EEPROM backend specifically: + +- one dirty cache window may be synchronized before the next cache window is staged during one logical write or erase; +- successful multi-window requests return only after the final dirty window is also durable; +- failed multi-window requests may still leave earlier windows committed while later windows remain old. + +Integration guidance: + +- size the flash-ee cache window so common parameter objects fit within one window whenever practical; +- avoid splitting one must-stay-consistent parameter group across multiple independently committed windows unless the application already has its own consistency/version marker. + +| Function | Description | +| --- | --- | +| `par_set_n_save(par_num, p_val)` | Set one parameter and persist it immediately. | +| `par_save_all()` | Persist all persistent parameters. | +| `par_save(par_num)` | Persist one parameter. | +| `par_save_by_id(par_id)` | Persist one parameter by external ID. | +| `par_save_clean()` | Rewrite the full NVM area managed by the module. | + +## Registration APIs + +These APIs register behavior per parameter. + +`par_register_on_change_cb()` is available only when `PAR_CFG_ENABLE_CHANGE_CALLBACK = 1`. + +`par_register_validation()` is available only when `PAR_CFG_ENABLE_RUNTIME_VALIDATION = 1`. + +| Function | Description | +| --- | --- | +| `par_register_on_change_cb(par_num, cb)` | Register a change callback for one parameter. Available only when `PAR_CFG_ENABLE_CHANGE_CALLBACK = 1`. | +| `par_register_validation(par_num, validation)` | Register a validation callback for one parameter. Available only when `PAR_CFG_ENABLE_RUNTIME_VALIDATION = 1`. | + +Example: + +```c +static void on_mode_change( + const par_num_t par_num, + const par_type_t new_val, + const par_type_t old_val) +{ + (void)par_num; + (void)new_val; + (void)old_val; +} + +static bool validate_mode(const par_num_t par_num, const par_type_t val) +{ + (void)par_num; + return (val.u8 <= 3U); +} + +static void app_hooks_init(void) +{ +#if ( 1 == PAR_CFG_ENABLE_CHANGE_CALLBACK ) + par_register_on_change_cb(ePAR_MODE, on_mode_change); +#endif + +#if ( 1 == PAR_CFG_ENABLE_RUNTIME_VALIDATION ) + par_register_validation(ePAR_MODE, validate_mode); +#endif +} +``` + +When enabled, these hooks affect runtime writes and explicit reset operations that use the normal setter path. They are not invoked during the internal startup default initialization performed by `par_init()`, by raw restore/reset paths, by typed fast setters, or by bitwise fast setters. + +Keep both hook types synchronous, short, and non-blocking. Do not perform long-running I/O, waits, sleeps, or other operations that may extend parameter-module lock hold time. Re-entering the parameter module from these hooks is an advanced usage pattern and should be reviewed carefully at application level. + +## Debug helpers + +Available only when debug support is enabled. + +| Function | Description | +| --- | --- | +| `par_get_status_str(status)` | Convert a status code to a debug string. | + +## Status categories + +`par_status_t` combines normal status, errors, and warnings. + +Common values include: + +- `ePAR_OK` +- `ePAR_ERROR` +- `ePAR_ERROR_INIT` +- `ePAR_ERROR_NVM` +- `ePAR_ERROR_CRC` +- `ePAR_ERROR_TYPE` +- `ePAR_ERROR_MUTEX` +- `ePAR_ERROR_VALUE` +- `ePAR_ERROR_PARAM` +- `ePAR_ERROR_PAR_NUM` +- `ePAR_WAR_SET_TO_DEF` +- `ePAR_WAR_NVM_REWRITTEN` +- `ePAR_WAR_NO_PERSISTENT` +- `ePAR_WAR_LIMITED` + + +The fixed 4-byte payload slot without a size descriptor cannot be used on a flash backend that requires 8-byte aligned writes, because the serialized record width is 7 bytes. diff --git a/docs/architecture.md b/docs/architecture.md new file mode 100644 index 0000000..dad13a3 --- /dev/null +++ b/docs/architecture.md @@ -0,0 +1,370 @@ +# Architecture + +This document explains how the module is structured internally and how the major pieces work together. + +## High-level model + +The module separates four concerns: + +1. **Generated parameter definition** through `par_table.def`, generated enums, and generated config structs +2. **Core runtime access** through `src/par.c`, `src/par.h`, and private implementation fragments included only by `src/par.c` +3. **Layout and storage** through `par_layout.*` and compile-time storage initialization fragments +4. **Optional platform and NVM integration** through `par_if.*`, `par_atomic.h`, and `par_nvm.*` + +Runtime validation hooks and on-change hooks are kept separate from the core `par_cfg_t` metadata table and can be compiled out independently. + +```mermaid +flowchart TD + A[par_table.def] --> B[par_def.h / par_def.c] + B --> C[Parameter configuration table] + B --> D[par_num_t enum] + B --> E[Compile-time storage counts] + C --> F[par.c runtime API] + E --> G[par_layout.c] + G --> H[Static typed storage] + F --> H + F --> I[ID hash map] + F --> J[Validation and callbacks] + F --> K[Optional NVM layer] + F --> L[Optional port backend] +``` + +## Single-source parameter definition + +`par_table.def` is intentionally included multiple times with different macro definitions. + +That makes one definition file drive multiple generated artifacts: + +- `par_num_t` enumeration in `par_def.h` +- configuration table access through `par_def.c` +- compile-time validation for integer parameter ranges +- compile-time storage group counts for layout +- fail-fast compile-time rejection for disabled `F32` entries + +This design reduces duplication and helps keep enum values, metadata, and storage assumptions aligned. + +### Why disabled `F32` still appears in enum expansion + +`par_def.h` intentionally keeps enum expansion configuration-independent. + +That keeps `par_num_t` generation stable and avoids include-order coupling with `par_cfg.h`. + +The fail-fast rule is enforced later in `par_def.c`: if `F32` support is disabled and `par_table.def` still contains `PAR_ITEM_F32(...)`, compilation stops with a static assertion. + + +## Internal vs external identification + +The module uses two identifiers for each parameter. + +### `par_num_t` + +`par_num_t` is the internal firmware-facing identifier. + +Use it when code inside the firmware directly accesses parameters. + +Characteristics: + +- dense enum-like index +- efficient for table-based access +- not intended to be stable across arbitrary reordering of the parameter list + +### `id` + +`id` is the external identifier stored in parameter metadata. + +Use it when parameters must be addressed by external systems such as: + +- CLI commands +- PC tools +- diagnostic frames +- protocol bridges + +This separation lets firmware use an efficient internal index while external tooling uses a stable numeric contract. + +## Storage model + +The module stores live values in static width-based groups instead of allocating each parameter separately on the heap. + +Storage groups: + +- 8-bit group for `U8`, `I8` +- 16-bit group for `U16`, `I16` +- 32-bit group for `U32`, `I32`, and, when enabled, `F32` + +Benefits: + +- predictable RAM usage +- no runtime heap dependency for live storage +- compact grouping by data width + +At runtime, each parameter resolves to: + +- a storage group based on its type +- an offset inside that group + +### How default values are applied at startup + +Live storage is initialized in two phases during startup: + +1. Integer default values from `par_table.def` are compiled directly into the grouped live storage object in `par.c`. +2. When `PAR_CFG_ENABLE_TYPE_F32 = 1`, `F32` default values are written into the grouped 32-bit storage member after layout offsets are available. + +The compile-time integer storage initializers are emitted through a private include fragment, `src/detail/par_storage_init.inc`, which is included only by `par.c` and initializes the grouped storage object (`U8/U16/U32` members). + +When `PAR_CFG_ENABLE_RESET_ALL_RAW = 1`, `par.c` keeps a grouped default mirror snapshot for raw reset. The snapshot preserves the same `U8/U16/U32` width-group storage semantics. + +This means `par_init()` does not need to apply startup defaults through the public setter path for every parameter. + +### Ordering contract for shared storage + +Within each width-based storage group, element order follows `par_table.def` entry order filtered by the types supported by that group. + +That ordering contract matters because: + +- compile-time integer default initializers depend on it +- runtime layout offset generation depends on it +- both sides must stay aligned so each parameter lands in the correct slot + +If the filtered storage order and the runtime layout scan order ever diverge, defaults can be written into the wrong storage positions. + +## Layout subsystem +When `PAR_CFG_ENABLE_TYPE_F32 = 1`, the layout step also makes it possible to patch `F32` defaults correctly, because floating-point values share the 32-bit storage group and need their final offsets first. + +The layout subsystem provides the offset map that connects each parameter to its location in the static typed storage. + +The layout step is also what makes it possible to patch `F32` defaults correctly, because floating-point values share the 32-bit storage group and need their final offsets first. + +### Compile-scan mode + +When: + +```c +#define PAR_CFG_LAYOUT_SOURCE PAR_CFG_LAYOUT_COMPILE_SCAN +``` + +`par_layout_init()` scans the parameter table during initialization and builds offsets dynamically. + +Use this mode when you want a self-contained integration with no external code generation step. + +### Script-layout mode + +When: + +```c +#define PAR_CFG_LAYOUT_SOURCE PAR_CFG_LAYOUT_SCRIPT +``` + +layout data is provided by a generated static header and consumed directly. + +Use this mode when your tooling already owns parameter generation or when you want fixed layout data before compilation. + +## Validation model + +Validation happens in two stages. + +### Compile-time validation + +Compile-time validation is used for integer parameter types: + +- `U8` +- `I8` +- `U16` +- `I16` +- `U32` +- `I32` + +Typical checks include: + +- `range.min <= range.max` +- `def >= range.min` +- `def <= range.max` + +These checks are generated from `par_table.def`, so invalid integer configurations fail at build time. + +`F32` range checks are still not evaluated as compile-time constant expressions. + +However, when `PAR_CFG_ENABLE_TYPE_F32 = 0`, any `PAR_ITEM_F32(...)` entry is rejected at compile time through a static assertion. This keeps `par_def.h` configuration-independent while still failing fast in `par_def.c`. + +### Runtime validation + +Runtime validation is used for checks that are better handled dynamically, including: + +- floating-point range validation +- `name != NULL` when name metadata is enabled +- `desc != NULL` when description metadata is enabled +- description policy checks through `par_port_is_desc_valid()` when enabled +- per-parameter application validation callbacks when `PAR_CFG_ENABLE_RUNTIME_VALIDATION = 1` + +This split keeps integer configuration errors out of the firmware image while still allowing flexible runtime policies. Runtime validation callbacks can be compiled out independently from the rest of the metadata model. + +## ID lookup path + +The module supports ID-based APIs such as `par_get_by_id()` and `par_set_by_id()`. All public checked write APIs (`par_set()`, `par_set_by_id()`, and typed setters) now converge on the same checked setter core so access enforcement, runtime validation, and on-change semantics remain consistent across entry points. + +Because external IDs do not need to be sequential, the build generates a static hash map from `par_table.def`. `par_init()` does not build the ID map at runtime. + +```mermaid +flowchart LR + A[External ID] --> B[Hash function] + B --> C[Bucket lookup] + C --> D[par_num_t] + D --> E[Checked public setter path] +``` + +### Collision policy + +The current implementation uses a strict one-entry-per-bucket map. + +That means: + +- duplicate IDs are rejected by compile-time table checks +- hash collisions are rejected by compile-time table checks +- optional runtime diagnostic scans can be enabled to print clearer startup logs for duplicate-ID and bucket-collision issues + +This keeps runtime lookup simple and deterministic, but it also means a conflicting ID assignment must be fixed at the source. + +### Access enforcement boundary + +`par_get_access()` remains metadata, but the metadata is now consumed by the checked public value-access boundary. Public write entry points (`par_set()`, `par_set_by_id()`, and typed setters) enforce external write capability and reject targets without `ePAR_ACCESS_WRITE` with `ePAR_ERROR_ACCESS`. Public read entry points (`par_get()`, `par_get_by_id()`, and typed getters) enforce external read capability and reject targets without `ePAR_ACCESS_READ` with the same status. + +Fast setters and internal restore paths intentionally remain outside that checked boundary. They are used for startup/default/NVM restore flows where the firmware must rehydrate trusted values without invoking public access-control policy. Metadata getters and `par_get_default()` also remain outside that boundary because they read the configuration table rather than the external-facing live-value path. + +Role-policy metadata is intentionally one layer higher. The core exposes `par_can_read()` / `par_can_write()` helper predicates, but it does not own login/session state or automatically apply role masks inside the generic value getters/setters. Integrators such as CLI, RPC, or diagnostic transports decide when to combine caller-role context with the access-capability bits. + +### Hash geometry and collision rule + +The current ID lookup implementation uses a strict one-entry-per-bucket hash map. + +Each external parameter ID is mapped to a bucket with the following multiplicative hash used by both static map generation and optional runtime diagnostics: + +```c +bucket = (((uint32_t)id * PAR_ID_HASH_GOLDEN_RATIO_32) >> (32u - PAR_ID_HASH_BITS)); +``` + +Where: + +* `PAR_ID_HASH_GOLDEN_RATIO_32 = 0x61C88647u` +* `PAR_ID_HASH_MIN_BUCKETS = 2 * ePAR_NUM_OF` +* `PAR_ID_HASH_BITS` is the smallest power-of-two bucket geometry that can hold at least `PAR_ID_HASH_MIN_BUCKETS` +* `PAR_ID_HASH_SIZE = 1u << PAR_ID_HASH_BITS` + +This design does **not** support probing or chaining. + +That means the current ID map effectively requires the active parameter table to be collision-free under the selected hash geometry: + +* two rows with the same `id` are invalid +* two different `id` values that land in the same bucket are also invalid + +In other words, the table must behave like a collision-free mapping for the configured bucket count. + +### Compile-time and runtime enforcement + +ID validity is enforced primarily at compile time: + +1. compile-time duplicate-ID and hash-bucket collision checks in `par_def.c` +2. compile-time static ID-map generation in `par_id_map_static.c` +3. optional runtime diagnostic scans in `par.c` + +Compile-time checks fail the build early when the parameter table already proves invalid. + +Optional runtime scans do not build the ID map. They exist only to provide clearer diagnostic logs during startup when additional field debugging is useful. + +### How to avoid hash collisions + +When assigning or changing external IDs in `par_table.def`: + +1. keep every `id` globally unique +2. avoid clustered numeric patterns that repeatedly land in the same hash bucket +3. re-run the build after every ID edit +4. if a collision is reported, change the conflicting external IDs in `par_table.def` +5. do not assume that "different IDs" are automatically safe; different IDs can still hash into the same bucket + +For the current implementation, avoiding collision means avoiding both: + +* duplicate `id` +* duplicate `PAR_HASH_ID_CONST(id)` result + +If frequent ID churn is expected, a probing-based or chained hash map is a more scalable design than relying on a collision-free table. + +## Normal path vs fast path + +Depending on build-time configuration, the normal path can include runtime validation callbacks and on-change callbacks. + +### Normal setters + +Normal setters are the default path. They are intended for ordinary application code where correctness and observability matter more than shaving off a few instructions. + +Depending on build-time configuration, the normal path can include runtime validation callbacks and on-change callbacks. + +The typed setter/getter implementations are emitted through `src/detail/par_typed_impl.inc`, a private include fragment included only by `par.c`. + +### Fast setters + +Fast setters are specialized APIs for controlled hot paths. They reduce overhead, but they should only be used when the surrounding code already guarantees the assumptions that the full path would normally check. + +Fast setters do not execute runtime validation callbacks or on-change callbacks. + +The bitwise fast helpers are emitted through `src/detail/par_bitwise_impl.inc`, another private include fragment included only by `par.c`. They are intentionally scoped as flags-only helpers for `U8` / `U16` / `U32` bitmask parameters and do not preserve normal setter range semantics. + +### Raw reset-all path + +When `PAR_CFG_ENABLE_RESET_ALL_RAW = 1`, `par_reset_all_to_default_raw()` is available as a storage-level reset path. + +It restores live values by one grouped `memcpy` from the default mirror snapshot, while still preserving the internal `U8/U16/U32` width-group storage model, so it bypasses: + +- runtime validation callbacks +- on-change callbacks +- normal setter range/flow semantics + +When `PAR_CFG_ENABLE_RESET_ALL_RAW = 1`, `par_set_all_to_default()` also uses this raw reset path for performance. + +## Optional NVM persistence + +When `PAR_CFG_NVM_EN = 1`, the module can persist selected parameters to NVM. + +NVM persistence uses a compile-time selected record layout that is intentionally different from the live RAM layout. Live RAM storage is grouped by value width (`U8` / `U16` / `U32` / optional `F32`) for efficient access and raw reset, while the managed NVM area stores a compile-time ordered slot list. The selected slot serializer can keep a fixed 4-byte payload slot with a size descriptor, keep the same fixed 4-byte payload slot without the size descriptor, store the natural payload width together with the size descriptor, store only `crc + payload` in compile-time persistent order, or store only `crc + payload` in grouped 8/16/32-bit persistent order. The two payload-only layouts intentionally rely on table-ID checking instead of per-record IDs to detect schema drift. + +The compile-time persistent order is the primary layout contract of the managed NVM image. Loader and rewrite paths restore slot `i` into compile-time persistent slot `i`, while the stored `id` remains inside each object as an integrity and diagnostics field rather than as the main startup lookup key. Count asymmetry is handled intentionally: a stored count larger than the live compile-time persistent count is treated as an incompatible image and triggers full rebuild, while a stored count smaller than the live count restores the common prefix first and then appends newly introduced tail slots from current defaults only for layouts whose prefix addresses stay stable. `GROUPED_PAYLOAD_ONLY` is excluded from that repair path and rebuilds on any stored/live count mismatch. Compatibility is checked against the stored prefix for every layout. Layouts with stored IDs include parameter IDs in that prefix digest, while the payload-only layouts exclude external parameter IDs and validate only prefix byte-layout compatibility; pure external-ID renumbering therefore does not invalidate an otherwise compatible stored prefix there. The fixed-slot layouts keep one 4-byte payload slot per persistent parameter. The compact-payload layout stores only the natural 1/2/4-byte payload width and therefore saves bytes for narrow types. The fixed-slot layout without a size descriptor is intentionally not offered when a flash backend requires 8-byte aligned writes, because its serialized record width is 7 bytes. + +The serialized NVM header is a fixed 12-byte storage image (`sign + obj_nb + table_id + crc16`) emitted explicitly instead of by writing raw struct memory. The header CRC covers the serialized native-order `obj_nb + table_id` bytes, so header corruption is detected separately from a valid header that simply belongs to another parameter-table schema. Each data object uses CRC-8 according to the selected record layout. This profile intentionally ties the persisted image to the current target architecture and does not normalize byte order for cross-platform migration. CRC calculation is routed through port hooks with bundled software defaults. The table-ID digest follows the same rule and hashes each scalar exactly as represented in native platform memory. For self-describing layouts, the digest covers `PAR_CFG_TABLE_ID_SCHEMA_VER`, selected persisted record layout, persistent-parameter count, persistent-parameter order, parameter type, and parameter ID. For payload-only layouts, the digest covers the stored persistent prefix count, persistent-parameter order, and parameter type, and intentionally excludes external parameter IDs. Default values, ranges, names, units, descriptions, and access flags are intentionally excluded because they do not change the serialized NVM object layout used by `par_nvm.c`. When table-ID checking is enabled, self-describing layouts treat add/remove/reorder/type/ID updates of persistent parameters as incompatible schema changes, `FIXED_PAYLOAD_ONLY` allows pure external-ID renumbering and compatible tail growth without forcing a rebuild, and `GROUPED_PAYLOAD_ONLY` still rebuilds on any stored/live count mismatch. If a payload-only prefix is semantically remapped while keeping the same serialized byte layout, the integrator must bump `PAR_CFG_TABLE_ID_SCHEMA_VER` explicitly because that remap is outside the automatic byte-layout digest. + +If the stored table-ID mismatches the live table-ID, the module treats the persisted image as incompatible and rebuilds the managed NVM area from current default values and the current schema. This can happen after an intentional schema/version bump, after parameter persistence layout changes, after stored-image corruption, or when the stored header count is larger than the current compile-time persistent count. If the stored count is smaller than the compile-time count, layouts with stable prefix addresses restore the stored prefix, append the missing tail slots from live defaults, and rewrite the header count to the current schema width; `GROUPED_PAYLOAD_ONLY` instead rebuilds on any stored/live count mismatch. The recovery action is centralized in `par_nvm_init()`: it accumulates status bits from header validation, table-ID validation, and payload loading, then decides whether to restore defaults only or restore defaults plus rewrite the managed NVM image. + +For this feature, `par_nvm.c` mounts a parameter-storage backend interface during initialization. The core no longer depends directly on a specific repository layout, while Kconfig selects which packaged backend adapter is built. +At the same time, the NVM core binds one compile-time selected persisted-record layout adapter. That adapter owns record address calculation, live-to-persist object preparation, stored-object validation, compatibility policy, and optional readback-compare logic. The common NVM flow therefore stays layout-agnostic even when layouts differ in stored `id`, size descriptors, prefix-append compatibility, or post-write verification semantics. +If the selected storage technology provides ECC or other media-health reporting, that signal is intentionally treated as input to the product policy rather than as an automatic parameter-core action. The business layer should decide whether to log only, rebuild/reset parameters, or escalate to a broader system-level error response. + +The module intentionally keeps a compile-time ordered slot image instead of introducing a free-layout scanned log. A scanned log would make layout recovery more flexible, but it would also add boot-time scan, compaction, and tombstone handling to a path whose slot order is already fixed by the parameter table. + +## Portability model + +The module stays portable by keeping platform-specific logic behind dedicated boundaries. + +Header placement follows the same rule: the main public entry header stays at `src/par.h`, while helper headers remain next to the implementation areas that own them. This keeps the package self-contained without introducing a separate `include/` tree before the API surface is stable. + +### Core portable layer + +Implemented under layered `src/` subdirectories: + +- `src/par.c`, `src/par.h`, and `src/par_cfg.h` for public runtime entry points and top-level orchestration +- `src/def/` for parameter definitions and generated static ID mapping +- `src/layout/` for storage layout calculation and accessors +- `src/persist/` for NVM persistence logic +- `src/persist/backend/` for reusable packaged backend adapters +- `src/port/` for platform-facing hooks used by the portable core +- `src/detail/` for private implementation fragments included only by `src/par.c` + +### Port-specific layer + +Implemented by the integrator as needed: + +- `par_cfg_port.h` +- `par_if_port.c` (optional strong override for the weak defaults in `src/port/par_if.c`, including mutex and CRC helpers) +- `par_atomic_port.h` + +This separation makes the core reusable while still allowing the target platform to provide mutexes, logging, assertions, and atomic primitives. Packaged storage backend adapters stay inside the `src/persist/backend/` subtree because they are reusable module integrations rather than board-specific port code, while repository-root files under `backend/` bind those portable backends to one concrete platform environment such as RT-Thread FAL or a product-specific native flash port. + + +## Flash-emulated EEPROM backend + +See `./flash-ee-backend-design.md` for the portable core and adapter design. diff --git a/docs/flash-ee-backend-design.md b/docs/flash-ee-backend-design.md new file mode 100644 index 0000000..9489f74 --- /dev/null +++ b/docs/flash-ee-backend-design.md @@ -0,0 +1,291 @@ +# Flash-Emulated EEPROM Backend Design + +This document describes the portable flash-emulated EEPROM backend used by the parameters package. It focuses on architecture, integration contracts, runtime constraints, and the public backend-facing API. + +## 1. Scope and intent + +The backend exposes a byte-addressable NVM interface to `par_nvm.c`, while persisting data on erase-before-write flash media. The implementation keeps the reusable core free of RT-Thread, FAL, HAL, or vendor flash SDK dependencies. Platform-specific flash access is supplied through a thin port adapter. + +The design target is parameter persistence, not a general-purpose flash translation layer. It is optimized for deterministic layout, bounded RAM, append-only commits, and simple recovery. + +## 2. High-level architecture + +```mermaid +flowchart TD + A[par_nvm.c] --> B[flash_ee backend API] + B --> C[RAM cache window] + B --> D[append log in active bank] + D --> E[bank header] + B --> F[checkpoint / bank swap] + F --> G[inactive bank] + B --> H[flash port adapter] + H --> I[FAL partition or native hooks] +``` + +The flash region exported by the port is split into exactly two banks: + +- one **active bank** that accepts append records +- one **inactive bank** used as the checkpoint target during rollover + +Each bank contains: + +- one fixed header +- zero or more fixed-size append records + +## 3. On-flash model + +### 3.1 Bank header + +The bank header stores: + +- magic +- backend format version +- logical size +- line size +- record size +- bank size +- monotonic sequence number +- header CRC +- state word + +A bank is considered selectable only when the header is structurally valid and matches the live geometry. + +### 3.2 Append record + +Each append record stores, in on-flash write order: + +- one fixed-size payload block +- one metadata block containing logical line index, payload size, and one record CRC over the payload plus those semantic metadata fields +- optional erased padding used to align the final commit unit +- one dedicated commit unit written last + +The payload block always has `line_size` bytes. Logical data is therefore reconstructed line-by-line rather than byte-by-byte. A record is treated as visible only when its final commit unit matches the committed pattern exactly. + +## 4. RAM cache model + +The backend keeps one bounded RAM cache window rather than a full logical image. + +- `logical_size` is the logical EEPROM address space exported upward. +- `cache_size` is the RAM staging window size. +- `line_size` is the granularity of one cached and appended line. + +The current implementation supports **exactly one dirty cache window at a time**. + +### Practical consequence + +- `read()` may reconstruct data outside the currently loaded clean window. +- `write()` and `erase()` may span multiple cache windows. +- before staging moves to a new window, the backend synchronizes the current dirty window automatically. +- a successful multi-window write or erase returns only after the final dirty window is synchronized as well. + +This is still a bounded single-window staging design, not a transparent multi-window write-back cache. It improves durability-on-success, but it is still not a transactional multi-window commit engine. + +## 5. Initialization and bank selection + +At init time the backend: + +1. binds and initializes the physical flash port +2. validates geometry and compile-time configuration +3. scans both banks +4. chooses the newest valid active bank by sequence number +5. formats bank 0 when neither bank is valid + +### Accepted end-of-log conditions during scan + +A bank scan stops successfully only when it reaches one of these conditions: + +1. one fully erased record slot, meaning the append log ended normally +2. one or more programmed slots whose final commit unit was never fully completed + +Any slot whose commit unit still contains erased bytes is treated as an uncommitted append and ignored during reconstruction. Any slot with a closed but invalid commit unit, and any committed record with invalid metadata or record CRC, makes the bank unusable. The record CRC covers the payload, logical line index, and payload size so corrupted metadata cannot silently redirect a valid payload to the wrong logical line. This fail-closed rule avoids silently accepting corrupted committed history while still tolerating interrupted appends. + +The append-record CRC semantics in backend format version `1` are part of the persisted-image contract, so banks written with incompatible record-integrity rules must still be rejected by the header version check and reformatted rather than being misinterpreted under a different integrity rule. + +## 6. Commit and checkpoint flow + +### 6.1 Normal sync + +`sync()` converts each dirty cache line into one append record in the active bank. + +For each appended line, the write order is: + +1. program the payload bytes +2. program the metadata block and any erased alignment padding +3. program the dedicated commit unit last + +There is no in-place record repair path. If power is lost before the final commit write completes, the partially written slot is treated as an uncommitted append and skipped during the next scan. Later sync operations append into subsequent free slots; cleanup happens only when checkpoint or bank rollover compacts the live image. + +### 6.2 Rollover / checkpoint + +When the active bank does not have enough space for the required append records, the backend: + +1. erases and prepares the inactive bank +2. reconstructs the live logical lines +3. appends the compacted image into the inactive bank +4. marks the new bank active by header-state transition +5. switches bank roles in RAM + +## 7. Recovery guarantees + +### 7.1 Request-completion semantics + +For this backend, `par_nvm_write(..., false)` must be read as "do not request an additional common-layer sync step" rather than "guarantee RAM-only staging until a later sync call". + +- the backend may synchronize an earlier dirty window before loading the next cache window; +- a successful write or erase returns only after the final dirty window from that request is durable in flash; +- a failed multi-window request is still non-transactional and may leave earlier windows committed while later windows remain old. + +### 7.2 Recovery guarantees + +The design intentionally prefers conservative recovery: + +- invalid headers reject the bank +- any programmed slot whose commit unit still contains erased bytes is ignored as uncommitted +- invalid committed records reject the bank +- uncommitted slots are skipped rather than repaired in place +- header state transition is used to make bank activation explicit + +This keeps recovery deterministic and avoids accepting ambiguous partially corrupted histories. + +## 8. Public flash-ee API + +## 8.1 Port binding + +### `par_store_backend_flash_ee_bind_port()` + +Binds one physical flash port implementation to the reusable core. + +The port implementation must provide: + +- `init` +- `deinit` +- `is_init` +- `read` +- `program` +- `erase` +- `get_region_size` +- `get_erase_size` +- `get_program_size` +- optional `get_name` + +## 8.2 Backend export + +### `par_store_backend_flash_ee_get_api()` + +Returns the backend API table consumed by the persistence layer. + +## 8.3 Diagnostics + +### `par_store_backend_flash_ee_get_diag()` + +Returns the latest backend-specific diagnostic code. + +### `par_store_backend_flash_ee_get_diag_str()` + +Returns a readable string for the diagnostic code. + +## 8.4 Diagnostic intent + +Important diagnostic classes include: + +- port binding / init / geometry errors +- header incompatibility errors +- invalid commit marker / range / CRC errors +- `record_tail` when one or more uncommitted append slots were skipped +- cache window contract violations +- capacity and checkpoint failures + +## 8.5 Record visibility contract + +The backend exposes a simple recovery contract to upper layers: + +- a record is visible only after its final commit unit is fully programmed +- payload and metadata written without the final commit unit are ignored after reboot +- the backend does not attempt in-place repair of an uncommitted append slot +- an uncommitted slot consumes physical space until the next checkpoint or bank rollover + +## 9. Integration constraints + +The following constraints are part of the backend contract. + +### 9.1 Flash region constraints + +The bound flash region must provide enough capacity for: + +- two banks +- one header per bank +- one fully compacted logical image in each bank +- at least one additional append-record slot in each bank after compaction + +The bank size must be erase-aligned. + +### 9.2 Program-size constraints + +The header size and record size must both be integer multiples of the physical program size. The record layout reserves one dedicated final commit unit whose size equals the physical program granularity, and the metadata area is padded so that commit-unit write can be issued as the final standalone program operation. + +### 9.3 Cache-window constraints + +The current implementation is a **single-dirty-window** design. + +Therefore: + +- upper layers must not assume arbitrary multi-window write coalescing +- a write or erase that crosses a cache-window boundary will trigger a sync before staging continues in the next window +- `cache_size` no longer has to cover the largest parameter payload, but it still shapes commit frequency and protection granularity + +If that trade-off is unacceptable for the target workload, this backend is the wrong persistence strategy for that integration. + +### 9.4 Parameter-layout guidance + +For parameter persistence workloads, the safest integration pattern is to reduce how often one logical save request must cross cache-window boundaries. + +Recommended practice: + +- choose `cache_size` so the common parameter object sizes for the product fit within one cache window whenever practical; +- avoid placing one must-stay-consistent parameter group across multiple independently committed windows; +- when one logically coupled group cannot fit in one window, add an application-level generation/version field or equivalent consistency marker so business logic can detect mixed-old/new state after a failed write. + +These recommendations are usually sufficient for ordinary configuration parameters where "latest save may be lost" is acceptable, but they are not a substitute for a true transactional storage format. + +### 9.5 Workload fit + +This backend fits: + +- parameter persistence with bounded update sets +- deterministic configuration blobs +- infrequent sync-driven commits + +This backend is not intended as: + +- a transparent multi-window random-write cache +- a general-purpose flash filesystem +- a high-frequency log store with large dirty working sets + +## 10. Port adapters in this package + +Two adapters are currently provided. + +### 10.1 FAL adapter + +`backend/par_store_backend_flash_ee_fal.c` + +Binds the core to one FAL partition. + +### 10.2 Native adapter + +`backend/par_store_backend_flash_ee_native.c` + +Binds the core to user-supplied native flash hooks without pulling in RT-Thread-specific headers. +The required hook ABI is declared in `parameters/src/persist/backend/par_store_backend_flash_ee.h`, and native-port integrations are expected to provide strong definitions for the required operational and geometry hooks at link time. + +## 11. Recommended configuration guidance + +Choose values so that: + +- `logical_size` matches the parameter NVM address space expected by `par_nvm.c` +- `line_size` balances append overhead against write amplification +- `cache_size` is large enough that common parameter objects usually fit inside one window +- any parameter set that must stay mutually consistent is either kept within one independently committed window or protected by an application-level consistency marker +- `program_size` matches the minimum flash program granularity of the concrete port + +For deployments that depend on full-image rewrite paths such as package-level store-all operations, choose `cache_size` based on the acceptable sync cadence and lock-hold budget rather than forcing `cache_size >= logical_size`. diff --git a/docs/getting-started.md b/docs/getting-started.md new file mode 100644 index 0000000..462f7bb --- /dev/null +++ b/docs/getting-started.md @@ -0,0 +1,498 @@ +# Getting started + +This guide shows how to integrate the `Device Parameters` module into a firmware project, which files you must provide, and which configuration choices matter first. + +## Integration checklist + +1. Add `src/par.c` and the needed sources from `src/def`, `src/layout`, `src/persist`, and `src/port` to your project. Add `src/persist/backend` only when you use the packaged backend adapter. +2. Provide `par_table.def` at the package root. +3. Provide `port/par_cfg_port.h`. +4. Decide whether you want: + - NVM persistence and which storage backend will implement it + - a platform-specific interface backend + - a platform-specific atomic backend + - compile-scan or script-provided layout + - raw reset-all support + - `F32` parameter support +5. Call `par_init()` before runtime access. +6. Use the typed APIs. Getter calls take an explicit output pointer and return `par_status_t`. When `PAR_CFG_ENABLE_ACCESS = 1`, checked public read APIs also return `ePAR_ERROR_ACCESS` for parameters without external read capability. + +## Required files + +### `par_table.def` + +`par_table.def` is the single source of truth for parameter definitions. + +Each row defines one parameter and is reused to build: + +- `par_num_t` +- the parameter configuration table +- compile-time integer validation checks +- compile-time storage counts +- optional role-based metadata columns used by `par_can_read()` / `par_can_write()` + +A minimal example: + +```c +PAR_ITEM_U8 ( + ePAR_MODE, + 10, + "Mode", + 0U, + 3U, + 0U, + NULL, + ePAR_ACCESS_RW, + ePAR_ROLE_ALL, + ePAR_ROLE_ALL, + true, + "Application operating mode" +) + +PAR_ITEM_F32( + ePAR_TARGET_TEMP, + 20, + "Target temperature", + -40.0f, + 125.0f, + 25.0f, + "degC", + ePAR_ACCESS_RW, + ePAR_ROLE_ALL, + ePAR_ROLE_ALL, + true, + "Requested control target temperature" +) +``` + +Use `template/par_table.deftmp` as the starting point. + +The `read_roles_` / `write_roles_` columns are part of the fixed `PAR_ITEM_*` row signature even when role policy is disabled. If you keep role policy disabled, use `ePAR_ROLE_ALL` / `ePAR_ROLE_NONE` as neutral placeholders in the extra role columns, or keep the template defaults and leave enforcement to the access bit only. + +This example requires `PAR_CFG_ENABLE_TYPE_F32 = 1`. If `F32` support is disabled, remove all `PAR_ITEM_F32(...)` rows from `par_table.def`. + +### `port/par_cfg_port.h` + +`src/par_cfg.h` includes `par_cfg_port.h` unconditionally. + +If you do not need platform overrides yet, start with a minimal stub: + +```c +#ifndef _PAR_CFG_PORT_H_ +#define _PAR_CFG_PORT_H_ +/* Optional platform overrides */ +#endif +``` + +Use `template/par_cfg_port.htmp` as the starting point. + +Keep `parameters/src` on the compiler include path so application code can include `par.h`. Also add the directory that contains your integration-owned `par_cfg_port.h` (and optionally `par_atomic_port.h`) to the compiler include path. + +## Optional integration files + +### `port/par_if_port.c` + +Provide this file only when `PAR_CFG_IF_PORT_EN = 1` **and** your target needs to override the core weak defaults. +Compile it as a normal source file (do not `#include` the `.c` file from core code). + +Use it to integrate platform-specific services such as: + +- initialization hooks +- mutex handling +- optional platform hooks unrelated to the core ID lookup hash map + +The ID lookup hash used by `par_get_by_id()` / `par_set_by_id()` is part of the core module and is generated at compile time, not supplied by `port/par_if_port.c`. +Do not confuse it with optional table-hash support used by NVM compatibility features. + +### `port/par_atomic_port.h` + +Provide this file only when the default C11 atomic backend is not suitable. + +Use it when: + +- your compiler does not support `` well enough +- your RTOS already provides atomic primitives +- you want all atomic operations mapped to a platform-native implementation + +Enable it with: + +```c +#define PAR_ATOMIC_BACKEND PAR_ATOMIC_BACKEND_PORT +``` + +### Static layout header + +Provide a generated static layout header only when: + +```c +#define PAR_CFG_LAYOUT_SOURCE PAR_CFG_LAYOUT_SCRIPT +``` + +Use `template/par_layout_static.htmp` as the contract for the generated file. + +The template includes the required count macros and offset-table declaration. It can also serve as the starting point for a project-specific file header banner. + +## Configuration decisions that matter first + +### NVM support + +Enable NVM only when you actually need persistent parameters. + +Relevant options in `par_cfg.h`: + +- `PAR_CFG_NVM_EN` +- `PAR_CFG_NVM_REGION` +- `PAR_CFG_ENABLE_ID` + +When `PAR_CFG_NVM_EN = 1`, persistence metadata in the parameter table is compiled in automatically. There is no separate `PAR_CFG_ENABLE_PERSIST` switch anymore. + +ID-based lookup is generated statically when `PAR_CFG_ENABLE_ID = 1`. Optional startup diagnostics can be enabled with: + +- `PAR_CFG_ENABLE_RUNTIME_ID_DUP_CHECK` +- `PAR_CFG_ENABLE_RUNTIME_ID_HASH_COLLISION_CHECK` + +When NVM is enabled, the parameters module requires a concrete packaged storage backend implementation. `src/persist/par_nvm.c` resolves and validates the backend API once during initialization, then uses the mounted callbacks directly for later reads, writes, erases, and sync operations. The RT-Thread AT24CXX backend and the portable flash-ee backend are both available. The flash-ee core is packaged under `src/persist/backend/` and can be integrated either through the repository-root `backend/par_store_backend_flash_ee_fal.c` bridge or through product-specific native flash hooks in the repository-root `backend/par_store_backend_flash_ee_native.c` adapter. The module can reuse an already-initialized backend or initialize it on demand and later deinitialize it only when it owns that initialization. Module deinit is conservative: it attempts backend and interface cleanup, and it clears the top-level module init state only after the owned child deinit steps succeed. +When `PAR_CFG_NVM_WRITE_VERIFY_EN = 1`, the NVM path also performs a backend sync plus readback verification after record writes and after header commits. The exact comparison rules are owned by the selected persisted-record layout adapter, not hard-coded in `par_nvm.c`. +If your storage medium provides ECC or similar read-health status, keep the policy decision in the business layer. The parameter module does not decide whether that condition means report-only, parameter reset/rebuild, or a wider product fault response. + +The persisted NVM image uses a compile-time selected record layout instead of the grouped-width RAM layout used at runtime. Compile-time persistent order remains the primary slot layout contract for startup restore. Layouts that serialize `id` still use it as a validation and diagnostics field inside each record, while the two payload-only fixed layouts omit the serialized `id` and resolve addresses entirely from the compile-time slot map. The fixed-slot layouts keep one 4-byte payload slot per persistent parameter, the compact-payload layout stores only the natural 1/2/4-byte payload width together with a size descriptor, and the two payload-only layouts store only `crc + payload` bytes. Because they do not serialize `id`, they require table-ID checking to stay enabled so schema changes still trigger a rebuild; their compatibility digest is computed from the stored persistent prefix and excludes external parameter IDs. `FIXED_PAYLOAD_ONLY` therefore allows pure ID renumbering and tail-slot growth without forcing a rebuild, while `GROUPED_PAYLOAD_ONLY` still rebuilds whenever the stored/live counts differ because regrouped addresses depend on the full live persistent set. If a payload-only prefix is semantically remapped while keeping the same serialized byte layout, the integrator must bump `PAR_CFG_TABLE_ID_SCHEMA_VER` explicitly because that change is outside the automatic digest. The fixed-slot layout without a size descriptor is intentionally unavailable for a flash backend that requires 8-byte aligned writes, because that serializer uses a 7-byte record, and the two variable-width payload-only layouts are hidden there for the same alignment reason. The header is serialized explicitly as a fixed 12-byte image (`sign + obj_nb + table_id + crc16`). Under the current single-target assumption, fields are stored, validated, and hashed in the native byte order of the running platform instead of being normalized to a cross-platform storage endianness. Header CRC-16 therefore covers the serialized native-order `obj_nb + table_id` bytes, each data object uses layout-specific CRC-8, and the compatibility digest is computed from native-order scalar images including the selected record layout. When the stored count is smaller than the compile-time persistent count, layouts with stable prefix addresses append the missing tail slots from current defaults and rewrite the header count; `GROUPED_PAYLOAD_ONLY` instead treats any stored/live count mismatch as incompatible and rebuilds. When the stored count is larger, startup treats the image as incompatible and rebuilds it. The bundled defaults are provided through `par_if_crc16_accumulate()` and `par_if_crc8_accumulate()`. + +Backend choices: + +- enable the packaged RT-Thread AT24CXX backend +- or enable the packaged flash-ee backend and choose either the FAL bridge or the native-flash hook path + +If `PAR_CFG_NVM_EN = 1` and the selected packaged backend implementation is not linked, the build fails at link time by design. + +### Logging + +Use `AUTOGEN_PM_USING_DEBUG` in the RT-Thread package Kconfig to enable package logs. The port layer now exposes leveled logging hooks (`INFO`, `DEBUG`, `WARN`, `ERROR`) and routes them through RT-Thread `rtdbg`/ULOG when enabled. + +### Layout source + +Choose one of these two modes: + +```c +#define PAR_CFG_LAYOUT_COMPILE_SCAN (0u) +#define PAR_CFG_LAYOUT_SCRIPT (1u) +``` + +Use **compile scan** when parameter definitions live in source and you want the module to derive offsets at initialization. + +Use **script layout** when your build or tooling already generates fixed layout data before compilation. + +### Atomic backend + +The default backend is C11 atomics. Switch to the port backend only when the default is not a good fit for the target. + +### Runtime hooks + +Use these options to control whether normal setters include runtime validation and on-change notifications: + +```c +#define PAR_CFG_ENABLE_RUNTIME_VALIDATION ( 1 ) +#define PAR_CFG_ENABLE_CHANGE_CALLBACK ( 1 ) +``` + +`PAR_CFG_ENABLE_RUNTIME_VALIDATION` controls whether normal setters call per-parameter validation callbacks registered through `par_register_validation()`. + +`PAR_CFG_ENABLE_CHANGE_CALLBACK` controls whether normal setters raise per-parameter change callbacks registered through `par_register_on_change_cb()`. + +These options are independent: + +- set both to `1` to keep the full normal setter behavior +- set either one to `0` to compile out that part of the runtime hook path +- fast setters still skip these hooks regardless of configuration + +When `PAR_CFG_ENABLE_RUNTIME_VALIDATION = 0`, `par_register_validation()` is not available. + +When `PAR_CFG_ENABLE_CHANGE_CALLBACK = 0`, `par_register_on_change_cb()` is not available. + + +### Raw reset-all API +Use `PAR_CFG_ENABLE_RESET_ALL_RAW` to control whether the raw reset-all API and default mirror storage are compiled in. + +```c +#define PAR_CFG_ENABLE_RESET_ALL_RAW ( 1 ) +``` + +When `PAR_CFG_ENABLE_RESET_ALL_RAW = 1`: + +* `par_reset_all_to_default_raw()` is available +* a grouped default mirror snapshot is kept in RAM for raw restore +* the grouped snapshot preserves the internal `U8/U16/U32` width-group storage layout +* `F32` defaults are mirrored into the 32-bit storage group as bit patterns after layout is known + +`par_reset_all_to_default_raw()` restores live values by copying the grouped storage snapshot and intentionally bypasses runtime validation callbacks, on-change callbacks, and range logic. + +The grouped default mirror snapshot is built before optional NVM load, so `par_reset_all_to_default_raw()` restores configured defaults, not persisted runtime values loaded from NVM. + +### F32 type support +Use `PAR_CFG_ENABLE_TYPE_F32` to control whether `F32` parameters are compiled into the module. + +```c +#define PAR_CFG_ENABLE_TYPE_F32 ( 1 ) +``` + +Set it to `0` only when your integration does not need floating-point parameters. + +When `PAR_CFG_ENABLE_TYPE_F32 = 0`: + +* `PAR_ITEM_F32(...)` entries are not allowed in `par_table.def` +* `par_set_f32()`, `par_get_f32()`, and `par_set_f32_fast()` are not available +* startup F32 default patching is skipped + +## Initialization + +Call `par_init()` before any runtime parameter access. + +```c +if (par_init() != ePAR_OK) +{ + /* Handle error */ +} +``` + +If `PAR_CFG_NVM_EN = 1`, NVM loading happens after the module applies default values from `par_table.def`, so persisted values can overwrite the startup defaults. +When raw reset-all is enabled, its grouped default mirror snapshot is built before that optional NVM load, so raw reset returns live storage to the configured defaults rather than to persisted NVM-loaded values. + +### How `par_init()` applies default values + +`par_init()` validates the table, binds the storage layout, initializes the interface layer, applies default values to live storage, and then optionally loads persisted values from NVM. + +During startup: + +- integer default values defined in `par_table.def` are already present in the grouped live storage object at definition time +- when `PAR_CFG_ENABLE_TYPE_F32 = 1`, `F32` default values are written into the grouped 32-bit storage member after layout offsets are known +- if NVM support is enabled, persisted values may then overwrite those default values + +Do not rely on startup initialization to trigger application callbacks or runtime validation hooks. + +## Reading and writing values + +The `F32` examples in this section require `PAR_CFG_ENABLE_TYPE_F32 = 1`. + +### Use the typed APIs in normal application code +```c +(void)par_set_f32(ePAR_TARGET_TEMP, (float32_t)42.5f); + +float32_t target_temp = 0.0f; +(void)par_get_f32(ePAR_TARGET_TEMP, &target_temp); +``` + +### Use typed APIs when explicitness matters + +```c +(void)par_set_u16(ePAR_PWM_LIMIT, 1024U); +uint16_t pwm_limit = 0U; +(void)par_get_u16(ePAR_PWM_LIMIT, &pwm_limit); +``` + +### Use pointer-based generic APIs only when needed + +```c +float32_t value = 12.0f; +(void)par_set(ePAR_TARGET_TEMP, &value); + +float32_t readback = 0.0f; +(void)par_get(ePAR_TARGET_TEMP, &readback); +``` + +## Registering callbacks and validation + +The registration APIs work per parameter and take the parameter number directly. + +`par_register_on_change_cb()` is available only when `PAR_CFG_ENABLE_CHANGE_CALLBACK = 1`. + +`par_register_validation()` is available only when `PAR_CFG_ENABLE_RUNTIME_VALIDATION = 1`. + +### On-change callback + +Use this only when `PAR_CFG_ENABLE_CHANGE_CALLBACK = 1`. Keep the callback synchronous, short, and non-blocking. Avoid long-running I/O, waits, sleeps, or other operations that may extend parameter-module lock hold time. + +```c +#if (1 == PAR_CFG_ENABLE_CHANGE_CALLBACK) +static void on_mode_changed( + const par_num_t par_num, + const par_type_t new_val, + const par_type_t old_val) +{ + (void)par_num; + /* React to the change */ +} + +static void app_register_callbacks(void) +{ + par_register_on_change_cb(ePAR_MODE, on_mode_changed); +} +#endif +``` + +### Validation callback + +Use this only when `PAR_CFG_ENABLE_RUNTIME_VALIDATION = 1`. Keep validation logic synchronous, short, and non-blocking. Avoid long-running I/O, waits, sleeps, or other operations that may extend parameter-module lock hold time. + +```c +#if (1 == PAR_CFG_ENABLE_RUNTIME_VALIDATION) +static bool validate_target_temp(const par_num_t par_num, const par_type_t val) +{ + (void)par_num; + + return (val.f32 >= -20.0f) && (val.f32 <= 100.0f); +} + +static void app_register_validation(void) +{ + par_register_validation(ePAR_TARGET_TEMP, validate_target_temp); +} +#endif +``` + +## Normal vs fast setters + +Use the normal setters unless you have a measured reason not to. + +### Normal setters + +Normal setters go through the normal runtime path. + +Depending on build-time configuration, that path can include runtime validation callbacks and on-change callbacks: + +- runtime validation callbacks are used only when `PAR_CFG_ENABLE_RUNTIME_VALIDATION = 1` +- on-change callbacks are raised only when `PAR_CFG_ENABLE_CHANGE_CALLBACK = 1` + +```c +(void)par_set_f32(ePAR_TARGET_TEMP, 25.0f); +``` + +### Fast setters + +Fast setters are meant for controlled hot paths where you accept reduced safety or observability in exchange for lower overhead. They do not run runtime validation callbacks or on-change callbacks. +```c +(void)par_set_u16_fast(ePAR_PWM_LIMIT, 1200U); +``` + +When you only have a typed value pointer and still want the unchecked path, use the generic fast dispatcher: +```c +float32_t value = 12.0f; +(void)par_set_fast(ePAR_TARGET_TEMP, &value); +``` + +Do not use fast setters as the default API for ordinary application code. + +### Bitwise fast setters + +Bitwise fast setters are the flags-only variant of the fast path. Use them only for `U8` / `U16` / `U32` parameters that represent bitmasks or status flags. They intentionally bypass runtime validation callbacks, on-change callbacks, and normal setter range semantics. + +```c +(void)par_bitor_set_u32_fast(ePAR_STATUS_FLAGS, STATUS_FLAG_READY); +(void)par_bitand_set_u32_fast(ePAR_STATUS_FLAGS, (uint32_t)(~STATUS_FLAG_ERROR)); +``` + +Good fits are enable masks, fault flags, and mode bits. Do not use bitwise fast setters as a substitute for ordinary numeric writes such as temperature, current limits, or thresholds. + +## Persistence to NVM + +When NVM support is enabled and a storage backend is linked, use the NVM APIs for storing current values. + +```c +if (par_save_all() != ePAR_OK) +{ + /* Handle storage error */ +} +``` + +Or update and store one parameter in one step: + +```c +uint32_t baud = 115200U; +(void)par_set_n_save(ePAR_UART_BAUD, &baud); +``` + +## Common mistakes to avoid + +- Forgetting to provide `par_cfg_port.h` +- Treating `par_num_t` as a stable external interface +- Using fast setters before understanding their tradeoffs +- Enabling NVM without linking any concrete parameter-storage backend +- Writing `par_table.def` entries with duplicate IDs +- Assigning different external IDs that still resolve to the same ID hash bucket +- Changing external IDs without rebuilding and checking the compile-time ID-map validation output +- Assuming the repository already ships a ready-to-build `par_table.def` for your project +- Disabling `PAR_CFG_ENABLE_TYPE_F32` while keeping `PAR_ITEM_F32(...)` entries in `par_table.def` +- Assuming `par_set_f32()` and `par_get_f32()` are still available after F32 support is disabled +- Registering validation or change callbacks without enabling the matching configuration macro + +### Compile-time error example when F32 support is disabled + +If `PAR_CFG_ENABLE_TYPE_F32 = 0` and `par_table.def` still contains `PAR_ITEM_F32(...)`, the build fails with a static assertion. + +Example: + +```log +def.h:124:51: error: size of array '_static_assert_ePAR_SYS_CPU_LOAD_MAX_f32_type_is_disabled__remove_PAR_ITEM_F32' is negative + 124 | #define _STATIC_ASSERT(name, expn) typedef char _static_assert_##name[(expn)?1:-1] + | ^~~~~~~~~~~~~~~ +port/par_cfg_port.h:130:44: note: in expansion of macro '_STATIC_ASSERT' + 130 | #define PAR_PORT_STATIC_ASSERT(name, expn) _STATIC_ASSERT(name, expn) + | ^~~~~~~~~~~~~~~~ +src/par_cfg.h:160:53: note: in expansion of macro 'PAR_PORT_STATIC_ASSERT' + 160 | #define PAR_STATIC_ASSERT(name, expn) PAR_PORT_STATIC_ASSERT(name, expn); + | ^~~~~~~~~~~~~~~~~~~~~~ +src/def/par_def.c:73:94: note: in expansion of macro 'PAR_STATIC_ASSERT' + 73 | #define PAR_CHECK_F32(enum_, id_, name_, min_, max_, def_, unit_, access_, read_roles_, write_roles_, pers_, desc_) PAR_STATIC_ASSERT(enum_##_f32_type_is_disabled__remove_PAR_ITEM_F32, 0) + | ^~~~~~~~~~~~~~~~~ +src/def/par_def.c:85:23: note: in expansion of macro 'PAR_CHECK_F32' + 85 | #define PAR_ITEM_F32 PAR_CHECK_F32 + | ^~~~~~~~~~~~~ +par_table.def:189:1: note: in expansion of macro 'PAR_ITEM_F32' + 189 | PAR_ITEM_F32(ePAR_SYS_CPU_LOAD_MAX, 10011, "CPU Max. load", 0.0f, 100.0f, 0.0f, "%", ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Maximum CPU load in %") + | ^~~~~~~~~~~~ +``` + +Fix the table first: remove the `PAR_ITEM_F32(...)` entry or re-enable `PAR_CFG_ENABLE_TYPE_F32`. + +### Compile-time error example when ID hash buckets collide + +The build also fails when two different external IDs resolve to the same hash bucket, because the static ID map requires a collision-free table under the configured hash geometry. + +This does not mean the IDs are equal. +It means the current one-entry-per-bucket ID map cannot represent both rows at the same time. + +Example: + +```log +par_table.def: In function 'par_compile_check_hash_bucket_collision': +src/def/par_def.c:156:105: error: duplicate case value + 156 | #define PAR_CHECK_ID_BUCKET_CASE(enum_, id_, name_, min_, max_, def_, unit_, access_, read_roles_, write_roles_, pers_, desc_) case PAR_HASH_ID_CONST(id_): break; + | ^~~~ +src/def/par_def.c:162:31: note: in expansion of macro 'PAR_CHECK_ID_BUCKET_CASE' + 162 | #define PAR_ITEM_U16 PAR_CHECK_ID_BUCKET_CASE + | ^~~~~~~~~~~~~~~~~~~~~~~~ +par_table.def:141:1: note: in expansion of macro 'PAR_ITEM_U16' + 141 | PAR_ITEM_U16(ePAR_CH3_VOL_RAW, 253, "Ch3 Raw Vout", ...) + | ^~~~~~~~~~~~ +src/def/par_def.c:156:105: note: previously used here + 156 | #define PAR_CHECK_ID_BUCKET_CASE(enum_, id_, name_, min_, max_, def_, unit_, access_, read_roles_, write_roles_, pers_, desc_) case PAR_HASH_ID_CONST(id_): break; + | ^~~~ +src/def/par_def.c:167:31: note: in expansion of macro 'PAR_CHECK_ID_BUCKET_CASE' + 167 | #define PAR_ITEM_F32 PAR_CHECK_ID_BUCKET_CASE + | ^~~~~~~~~~~~~~~~~~~~~~~~ +par_table.def:54:1: note: in expansion of macro 'PAR_ITEM_F32' + 54 | PAR_ITEM_F32(ePAR_CH1_TSIM, 20, "Ch1 Ref Temperature", ...) + | ^~~~~~~~~~~~ +``` + +Read this error as: + +* the two IDs are different +* but `PAR_HASH_ID_CONST(253)` and `PAR_HASH_ID_CONST(20)` produced the same bucket index +* the current ID map cannot accept both rows + +Fix the table first: + +1. keep IDs unique +2. change one of the conflicting external IDs in `par_table.def` +3. rebuild until the compile-time collision check no longer fails + +If collisions become frequent, reconsider the ID assignment policy or replace the one-entry-per-bucket map with a probing-based implementation. diff --git a/src/def/par_def.c b/src/def/par_def.c new file mode 100644 index 0000000..ee96e92 --- /dev/null +++ b/src/def/par_def.c @@ -0,0 +1,521 @@ +/** + * @file par_def.c + * @brief Build parameter-definition tables and derived metadata. + * @author wdfk-prog + * @version 1.0 + * @date 2026-03-27 + * + * @copyright Copyright (c) 2026 Ziga Miklosic. Distributed under the MIT license. + * + * @note : + * @par Change Log: + * Date Version Author Description + * 2026-03-27 1.0 wdfk-prog first version + */ +/** + * @addtogroup PAR_CFG + * @{ + * + * @brief Configuration for device parameters. + * + * User shall put code inside inside code block start with. + * "USER_CODE_BEGIN" and with end of "USER_CODE_END". + */ +/** + * @brief Include dependencies. + */ +#include "def/par_def.h" +#include "par.h" +/** + * @brief Compile-time definitions. + */ +/** + * @brief Shared compile-time range checks for integer parameter items. + */ +#if (1 == PAR_CFG_ENABLE_RANGE) +#define PAR_CHECK_INT_COMMON(enum_, min_, max_, def_) \ + PAR_STATIC_ASSERT(enum_##_min_lt_max, ((min_) < (max_))); \ + PAR_STATIC_ASSERT(enum_##_def_ge_min, ((def_) >= (min_))); \ + PAR_STATIC_ASSERT(enum_##_def_le_max, ((def_) <= (max_))) + +/** + * @brief Compile-time checks for each parameter value type. + * @details Signature: + * (enum_, id_, name_, min_, max_, def_, unit_, access_, read_roles_, write_roles_, pers_, desc_). + */ +#define PAR_CHECK_U8(enum_, id_, name_, min_, max_, def_, unit_, access_, read_roles_, write_roles_, pers_, desc_) PAR_CHECK_INT_COMMON(enum_, min_, max_, def_) +#define PAR_CHECK_U16(enum_, id_, name_, min_, max_, def_, unit_, access_, read_roles_, write_roles_, pers_, desc_) PAR_CHECK_INT_COMMON(enum_, min_, max_, def_) +#define PAR_CHECK_U32(enum_, id_, name_, min_, max_, def_, unit_, access_, read_roles_, write_roles_, pers_, desc_) PAR_CHECK_INT_COMMON(enum_, min_, max_, def_) +#define PAR_CHECK_I8(enum_, id_, name_, min_, max_, def_, unit_, access_, read_roles_, write_roles_, pers_, desc_) PAR_CHECK_INT_COMMON(enum_, min_, max_, def_) +#define PAR_CHECK_I16(enum_, id_, name_, min_, max_, def_, unit_, access_, read_roles_, write_roles_, pers_, desc_) PAR_CHECK_INT_COMMON(enum_, min_, max_, def_) +#define PAR_CHECK_I32(enum_, id_, name_, min_, max_, def_, unit_, access_, read_roles_, write_roles_, pers_, desc_) PAR_CHECK_INT_COMMON(enum_, min_, max_, def_) +#else +#define PAR_CHECK_U8(enum_, id_, name_, min_, max_, def_, unit_, access_, read_roles_, write_roles_, pers_, desc_) +#define PAR_CHECK_U16(enum_, id_, name_, min_, max_, def_, unit_, access_, read_roles_, write_roles_, pers_, desc_) +#define PAR_CHECK_U32(enum_, id_, name_, min_, max_, def_, unit_, access_, read_roles_, write_roles_, pers_, desc_) +#define PAR_CHECK_I8(enum_, id_, name_, min_, max_, def_, unit_, access_, read_roles_, write_roles_, pers_, desc_) +#define PAR_CHECK_I16(enum_, id_, name_, min_, max_, def_, unit_, access_, read_roles_, write_roles_, pers_, desc_) +#define PAR_CHECK_I32(enum_, id_, name_, min_, max_, def_, unit_, access_, read_roles_, write_roles_, pers_, desc_) +#endif +/** + * @brief NOTE: F32 range checks are runtime-only. + * @details Some embedded/legacy GCC toolchains do not reliably treat float comparisons. + * in static assertions as integer constant expressions, and may emit. + * "variably modified '_static_assert_...' at file scope". + */ +#if (1 == PAR_CFG_ENABLE_TYPE_F32) +#define PAR_CHECK_F32(enum_, id_, name_, min_, max_, def_, unit_, access_, read_roles_, write_roles_, pers_, desc_) +#else +#define PAR_CHECK_F32(enum_, id_, name_, min_, max_, def_, unit_, access_, read_roles_, write_roles_, pers_, desc_) PAR_STATIC_ASSERT(enum_##_f32_type_is_disabled__remove_PAR_ITEM_F32, 0) +#endif + +/** + * @brief Dispatch map for compile-time checks. + */ +#define PAR_ITEM_U8 PAR_CHECK_U8 +#define PAR_ITEM_U16 PAR_CHECK_U16 +#define PAR_ITEM_U32 PAR_CHECK_U32 +#define PAR_ITEM_I8 PAR_CHECK_I8 +#define PAR_ITEM_I16 PAR_CHECK_I16 +#define PAR_ITEM_I32 PAR_CHECK_I32 +#define PAR_ITEM_F32 PAR_CHECK_F32 + +#include "../../par_table.def" + +#undef PAR_ITEM_U8 +#undef PAR_ITEM_U16 +#undef PAR_ITEM_U32 +#undef PAR_ITEM_I8 +#undef PAR_ITEM_I16 +#undef PAR_ITEM_I32 +#undef PAR_ITEM_F32 + +#undef PAR_CHECK_U8 +#undef PAR_CHECK_U16 +#undef PAR_CHECK_U32 +#undef PAR_CHECK_I8 +#undef PAR_CHECK_I16 +#undef PAR_CHECK_I32 +#undef PAR_CHECK_F32 +#if (1 == PAR_CFG_ENABLE_RANGE) +#undef PAR_CHECK_INT_COMMON +#endif + +#if (1 == PAR_CFG_ENABLE_ID) +/** + * @brief Compile-time check A: duplicated parameter IDs in par_table.def. + * + * @note Duplicate ID values trigger duplicated "case" labels. + */ +#define PAR_CHECK_ID_DUPLICATE_CASE(enum_, id_, name_, min_, max_, def_, unit_, access_, read_roles_, write_roles_, pers_, desc_) \ + case ((uint32_t)(id_)): \ + break; +static void par_compile_check_duplicate_ids(void) +{ + switch (0u) + { +#define PAR_ITEM_U8 PAR_CHECK_ID_DUPLICATE_CASE +#define PAR_ITEM_U16 PAR_CHECK_ID_DUPLICATE_CASE +#define PAR_ITEM_U32 PAR_CHECK_ID_DUPLICATE_CASE +#define PAR_ITEM_I8 PAR_CHECK_ID_DUPLICATE_CASE +#define PAR_ITEM_I16 PAR_CHECK_ID_DUPLICATE_CASE +#define PAR_ITEM_I32 PAR_CHECK_ID_DUPLICATE_CASE +#define PAR_ITEM_F32 PAR_CHECK_ID_DUPLICATE_CASE +#include "../../par_table.def" +#undef PAR_ITEM_U8 +#undef PAR_ITEM_U16 +#undef PAR_ITEM_U32 +#undef PAR_ITEM_I8 +#undef PAR_ITEM_I16 +#undef PAR_ITEM_I32 +#undef PAR_ITEM_F32 + default: + break; + } +} + +/** + * @brief Compile-time check B: external ID hash-bucket collisions in par_table.def. + * @details The runtime ID map is a strict one-entry-per-bucket structure and does not. + * implement probing or chaining. + * Therefore two different external IDs are still invalid when. + * PAR_HASH_ID_CONST(id_a) == PAR_HASH_ID_CONST(id_b). + * This check intentionally fails the build early by generating duplicated. + * "case" labels for colliding bucket indices. + */ +#define PAR_CHECK_ID_BUCKET_CASE(enum_, id_, name_, min_, max_, def_, unit_, access_, read_roles_, write_roles_, pers_, desc_) \ + case PAR_HASH_ID_CONST(id_): \ + break; +static void par_compile_check_hash_bucket_collision(void) +{ + switch (0u) + { +#define PAR_ITEM_U8 PAR_CHECK_ID_BUCKET_CASE +#define PAR_ITEM_U16 PAR_CHECK_ID_BUCKET_CASE +#define PAR_ITEM_U32 PAR_CHECK_ID_BUCKET_CASE +#define PAR_ITEM_I8 PAR_CHECK_ID_BUCKET_CASE +#define PAR_ITEM_I16 PAR_CHECK_ID_BUCKET_CASE +#define PAR_ITEM_I32 PAR_CHECK_ID_BUCKET_CASE +#define PAR_ITEM_F32 PAR_CHECK_ID_BUCKET_CASE +#include "../../par_table.def" +#undef PAR_ITEM_U8 +#undef PAR_ITEM_U16 +#undef PAR_ITEM_U32 +#undef PAR_ITEM_I8 +#undef PAR_ITEM_I16 +#undef PAR_ITEM_I32 +#undef PAR_ITEM_F32 + default: + break; + } +} + +/** + * @brief Keep compile-check helper functions "used" to avoid unused-function warnings. + */ +PAR_STATIC_ASSERT(par_compile_check_duplicate_ids_ref, (sizeof(&par_compile_check_duplicate_ids) > 0u)); +PAR_STATIC_ASSERT(par_compile_check_hash_bucket_collision_ref, (sizeof(&par_compile_check_hash_bucket_collision) > 0u)); + +#undef PAR_CHECK_ID_DUPLICATE_CASE +#undef PAR_CHECK_ID_BUCKET_CASE +#endif +/** + * @brief Module-scope variables. + */ +/** + * Parameters definitions. + * + * @brief + * + * Each defined parameter has following properties: + * + * i) Parameter ID: Unique parameter identification number. ID shall not be duplicated. + * ii) Name: Parameter name. Max. length of 32 chars. + * iii) Min: Parameter minimum value. Min value must be less than max value. + * iv) Max: Parameter maximum value. Max value must be more than min value. + * v) Def: Parameter default value. Default value must lie between interval: [min, max]. + * vi) Unit: In case parameter shows physical value. Max. length of 32 chars. + * vii) Data type: Parameter data type. Supported types: uint8_t, int8_t, uint16_t, int16_t, uint32_t, int32_t and float32_t. + * viii) Access: Access type visible from external device such as PC. Either ReadWrite or ReadOnly. + * ix) Persistence: Tells if parameter value is being written into NVM. + * + * + * @note User shall fill up wanted parameter definitions! + */ +/** + * @brief X-Macro table initializers for each parameter value type. + * @details Signature: + * (enum_, id_, name_, min_, max_, def_, unit_, access_, read_roles_, write_roles_, pers_, desc_). + */ +#if (1 == PAR_CFG_ENABLE_ID) +#define PAR_INIT_ID(id_) .id = (uint16_t)(id_), +#else +#define PAR_INIT_ID(id_) +#endif + +#if (1 == PAR_CFG_ENABLE_NAME) +#define PAR_INIT_NAME(name_) .name = (name_), +#else +#define PAR_INIT_NAME(name_) +#endif + +#if (1 == PAR_CFG_ENABLE_RANGE) +#define PAR_INIT_RANGE_U8(min_, max_) .range.min.u8 = (uint8_t)(min_), .range.max.u8 = (uint8_t)(max_), +#define PAR_INIT_RANGE_U16(min_, max_) .range.min.u16 = (uint16_t)(min_), .range.max.u16 = (uint16_t)(max_), +#define PAR_INIT_RANGE_U32(min_, max_) .range.min.u32 = (uint32_t)(min_), .range.max.u32 = (uint32_t)(max_), +#define PAR_INIT_RANGE_I8(min_, max_) .range.min.i8 = (int8_t)(min_), .range.max.i8 = (int8_t)(max_), +#define PAR_INIT_RANGE_I16(min_, max_) .range.min.i16 = (int16_t)(min_), .range.max.i16 = (int16_t)(max_), +#define PAR_INIT_RANGE_I32(min_, max_) .range.min.i32 = (int32_t)(min_), .range.max.i32 = (int32_t)(max_), +#define PAR_INIT_RANGE_F32(min_, max_) .range.min.f32 = (float32_t)(min_), .range.max.f32 = (float32_t)(max_), +#else +#define PAR_INIT_RANGE_U8(min_, max_) +#define PAR_INIT_RANGE_U16(min_, max_) +#define PAR_INIT_RANGE_U32(min_, max_) +#define PAR_INIT_RANGE_I8(min_, max_) +#define PAR_INIT_RANGE_I16(min_, max_) +#define PAR_INIT_RANGE_I32(min_, max_) +#define PAR_INIT_RANGE_F32(min_, max_) +#endif + +#if (1 == PAR_CFG_ENABLE_UNIT) +#define PAR_INIT_UNIT(unit_) .unit = (unit_), +#else +#define PAR_INIT_UNIT(unit_) +#endif + +#if (1 == PAR_CFG_ENABLE_ACCESS) +#define PAR_INIT_ACCESS(access_) .access = (access_), +#else +#define PAR_INIT_ACCESS(access_) +#endif + +#if (1 == PAR_CFG_ENABLE_ROLE_POLICY) +#define PAR_INIT_READ_ROLES(read_roles_) .read_roles = (read_roles_), +#define PAR_INIT_WRITE_ROLES(write_roles_) .write_roles = (write_roles_), +#else +#define PAR_INIT_READ_ROLES(read_roles_) +#define PAR_INIT_WRITE_ROLES(write_roles_) +#endif + +#if (1 == PAR_CFG_NVM_EN) +/** + * @brief Translate the X-Macro persistence column into a stored persist slot index. + * + * @details The pers_ argument in par_table.def is written as true/false. Because + * expands those tokens to 1/0, the two-step helper first lets pers_ + * expand normally, then token-pastes the result into either: + * - PAR_PERSIST_IDX_VALUE_1(enum_) for persistent entries + * - PAR_PERSIST_IDX_VALUE_0(enum_) for non-persistent entries + * + * The _1 branch returns the dense compile-time slot constant + * PAR_PERSIST_IDX_. + * The _0 branch returns PAR_PERSIST_IDX_INVALID, because non-persistent + * parameters do not own any slot in the managed NVM image. + * + * Two macro layers are required here because macro arguments are not expanded + * before token pasting with ##. GCC documents this prescan rule explicitly. + */ +#define PAR_PERSIST_IDX_VALUE(enum_, pers_) PAR_PERSIST_IDX_VALUE_I(enum_, pers_) +#define PAR_PERSIST_IDX_VALUE_I(enum_, pers_) PAR_PERSIST_IDX_VALUE_##pers_(enum_) +#define PAR_PERSIST_IDX_VALUE_1(enum_) PAR_PERSIST_IDX_##enum_ +#define PAR_PERSIST_IDX_VALUE_0(enum_) PAR_PERSIST_IDX_INVALID +#define PAR_INIT_PERSIST(enum_, pers_) .persistent = (pers_), .persist_idx = PAR_PERSIST_IDX_VALUE(enum_, pers_), +#else +#define PAR_INIT_PERSIST(enum_, pers_) +#endif + +#if (1 == PAR_CFG_ENABLE_DESC) +#define PAR_INIT_DESC(desc_) .desc = (desc_), +#else +#define PAR_INIT_DESC(desc_) +#endif + +#define PAR_INIT_U8(enum_, id_, name_, min_, max_, def_, unit_, access_, read_roles_, write_roles_, pers_, desc_) \ + [enum_] = { \ + PAR_INIT_ID(id_) \ + PAR_INIT_NAME(name_) \ + PAR_INIT_RANGE_U8(min_, max_) \ + .def.u8 = (uint8_t)(def_), \ + PAR_INIT_UNIT(unit_) \ + .type = ePAR_TYPE_U8, \ + PAR_INIT_ACCESS(access_) \ + PAR_INIT_READ_ROLES(read_roles_) \ + PAR_INIT_WRITE_ROLES(write_roles_) \ + PAR_INIT_PERSIST(enum_, pers_) \ + PAR_INIT_DESC(desc_) \ + }, + +#define PAR_INIT_U16(enum_, id_, name_, min_, max_, def_, unit_, access_, read_roles_, write_roles_, pers_, desc_) \ + [enum_] = { \ + PAR_INIT_ID(id_) \ + PAR_INIT_NAME(name_) \ + PAR_INIT_RANGE_U16(min_, max_) \ + .def.u16 = (uint16_t)(def_), \ + PAR_INIT_UNIT(unit_) \ + .type = ePAR_TYPE_U16, \ + PAR_INIT_ACCESS(access_) \ + PAR_INIT_READ_ROLES(read_roles_) \ + PAR_INIT_WRITE_ROLES(write_roles_) \ + PAR_INIT_PERSIST(enum_, pers_) \ + PAR_INIT_DESC(desc_) \ + }, + +#define PAR_INIT_U32(enum_, id_, name_, min_, max_, def_, unit_, access_, read_roles_, write_roles_, pers_, desc_) \ + [enum_] = { \ + PAR_INIT_ID(id_) \ + PAR_INIT_NAME(name_) \ + PAR_INIT_RANGE_U32(min_, max_) \ + .def.u32 = (uint32_t)(def_), \ + PAR_INIT_UNIT(unit_) \ + .type = ePAR_TYPE_U32, \ + PAR_INIT_ACCESS(access_) \ + PAR_INIT_READ_ROLES(read_roles_) \ + PAR_INIT_WRITE_ROLES(write_roles_) \ + PAR_INIT_PERSIST(enum_, pers_) \ + PAR_INIT_DESC(desc_) \ + }, + +#define PAR_INIT_I8(enum_, id_, name_, min_, max_, def_, unit_, access_, read_roles_, write_roles_, pers_, desc_) \ + [enum_] = { \ + PAR_INIT_ID(id_) \ + PAR_INIT_NAME(name_) \ + PAR_INIT_RANGE_I8(min_, max_) \ + .def.i8 = (int8_t)(def_), \ + PAR_INIT_UNIT(unit_) \ + .type = ePAR_TYPE_I8, \ + PAR_INIT_ACCESS(access_) \ + PAR_INIT_READ_ROLES(read_roles_) \ + PAR_INIT_WRITE_ROLES(write_roles_) \ + PAR_INIT_PERSIST(enum_, pers_) \ + PAR_INIT_DESC(desc_) \ + }, + +#define PAR_INIT_I16(enum_, id_, name_, min_, max_, def_, unit_, access_, read_roles_, write_roles_, pers_, desc_) \ + [enum_] = { \ + PAR_INIT_ID(id_) \ + PAR_INIT_NAME(name_) \ + PAR_INIT_RANGE_I16(min_, max_) \ + .def.i16 = (int16_t)(def_), \ + PAR_INIT_UNIT(unit_) \ + .type = ePAR_TYPE_I16, \ + PAR_INIT_ACCESS(access_) \ + PAR_INIT_READ_ROLES(read_roles_) \ + PAR_INIT_WRITE_ROLES(write_roles_) \ + PAR_INIT_PERSIST(enum_, pers_) \ + PAR_INIT_DESC(desc_) \ + }, + +#define PAR_INIT_I32(enum_, id_, name_, min_, max_, def_, unit_, access_, read_roles_, write_roles_, pers_, desc_) \ + [enum_] = { \ + PAR_INIT_ID(id_) \ + PAR_INIT_NAME(name_) \ + PAR_INIT_RANGE_I32(min_, max_) \ + .def.i32 = (int32_t)(def_), \ + PAR_INIT_UNIT(unit_) \ + .type = ePAR_TYPE_I32, \ + PAR_INIT_ACCESS(access_) \ + PAR_INIT_READ_ROLES(read_roles_) \ + PAR_INIT_WRITE_ROLES(write_roles_) \ + PAR_INIT_PERSIST(enum_, pers_) \ + PAR_INIT_DESC(desc_) \ + }, + +#define PAR_INIT_F32(enum_, id_, name_, min_, max_, def_, unit_, access_, read_roles_, write_roles_, pers_, desc_) \ + [enum_] = { \ + PAR_INIT_ID(id_) \ + PAR_INIT_NAME(name_) \ + PAR_INIT_RANGE_F32(min_, max_) \ + .def.f32 = (float32_t)(def_), \ + PAR_INIT_UNIT(unit_) \ + .type = ePAR_TYPE_F32, \ + PAR_INIT_ACCESS(access_) \ + PAR_INIT_READ_ROLES(read_roles_) \ + PAR_INIT_WRITE_ROLES(write_roles_) \ + PAR_INIT_PERSIST(enum_, pers_) \ + PAR_INIT_DESC(desc_) \ + }, + +#define PAR_ITEM_U8 PAR_INIT_U8 +#define PAR_ITEM_U16 PAR_INIT_U16 +#define PAR_ITEM_U32 PAR_INIT_U32 +#define PAR_ITEM_I8 PAR_INIT_I8 +#define PAR_ITEM_I16 PAR_INIT_I16 +#define PAR_ITEM_I32 PAR_INIT_I32 +#define PAR_ITEM_F32 PAR_INIT_F32 + +static const par_cfg_t g_par_table[ePAR_NUM_OF] = { +#include "../../par_table.def" +}; + +#undef PAR_ITEM_U8 +#undef PAR_ITEM_U16 +#undef PAR_ITEM_U32 +#undef PAR_ITEM_I8 +#undef PAR_ITEM_I16 +#undef PAR_ITEM_I32 +#undef PAR_ITEM_F32 + +#undef PAR_INIT_U8 +#undef PAR_INIT_U16 +#undef PAR_INIT_U32 +#undef PAR_INIT_I8 +#undef PAR_INIT_I16 +#undef PAR_INIT_I32 +#undef PAR_INIT_F32 +#undef PAR_INIT_ID +#undef PAR_INIT_NAME +#undef PAR_INIT_RANGE_U8 +#undef PAR_INIT_RANGE_U16 +#undef PAR_INIT_RANGE_U32 +#undef PAR_INIT_RANGE_I8 +#undef PAR_INIT_RANGE_I16 +#undef PAR_INIT_RANGE_I32 +#undef PAR_INIT_RANGE_F32 +#undef PAR_INIT_UNIT +#undef PAR_INIT_ACCESS +#undef PAR_INIT_PERSIST +#undef PAR_INIT_DESC +#if (1 == PAR_CFG_NVM_EN) +#undef PAR_PERSIST_IDX_VALUE +#undef PAR_PERSIST_IDX_VALUE_I +#undef PAR_PERSIST_IDX_VALUE_1 +#undef PAR_PERSIST_IDX_VALUE_0 +#endif + +/** + * @brief Configuration-independent compile-time parameter-ID table. + */ +#define PAR_ITEM_ID_VALUE(enum_, id_, name_, min_, max_, def_, unit_, access_, read_roles_, write_roles_, pers_, desc_) [enum_] = (uint16_t)(id_), +static const uint16_t g_par_id_table[ePAR_NUM_OF] = { +#define PAR_ITEM_U8 PAR_ITEM_ID_VALUE +#define PAR_ITEM_U16 PAR_ITEM_ID_VALUE +#define PAR_ITEM_U32 PAR_ITEM_ID_VALUE +#define PAR_ITEM_I8 PAR_ITEM_ID_VALUE +#define PAR_ITEM_I16 PAR_ITEM_ID_VALUE +#define PAR_ITEM_I32 PAR_ITEM_ID_VALUE +#define PAR_ITEM_F32 PAR_ITEM_ID_VALUE +#include "../../par_table.def" +#undef PAR_ITEM_U8 +#undef PAR_ITEM_U16 +#undef PAR_ITEM_U32 +#undef PAR_ITEM_I8 +#undef PAR_ITEM_I16 +#undef PAR_ITEM_I32 +#undef PAR_ITEM_F32 +}; +#undef PAR_ITEM_ID_VALUE + +/** + * @brief Table size in bytes. + */ +static const uint32_t gu32_par_table_size = sizeof(g_par_table); + +/** + * @brief Compile-time derived number of parameters flagged persistent. + */ +const uint16_t g_par_persistent_count = (uint16_t)PAR_PERSISTENT_COMPILE_COUNT; +/** + * @brief Function declarations and definitions. + */ +/** + * @brief Get full Device Parameter configuration table. + * + * @return pointer to configuration table. + */ +const par_cfg_t *par_cfg_get_table(void) +{ + return g_par_table; +} +/** + * @brief Get single Device Parameter configuration. + * + * @return pointer to parameter config. + */ +const par_cfg_t *par_cfg_get(const par_num_t par_num) +{ + return &g_par_table[par_num]; +} + +/** + * @brief Return the compile-time parameter ID for one entry. + * + * @param par_num Parameter number. + * @return Parameter ID from the generated parameter table. + */ +uint16_t par_cfg_get_param_id_const(const par_num_t par_num) +{ + PAR_ASSERT(par_num < ePAR_NUM_OF); + return g_par_id_table[par_num]; +} +/** + * @brief Get configuration table size in bytes. + * + * @return Size of table in bytes. + */ +uint32_t par_cfg_get_table_size(void) +{ + return gu32_par_table_size; +} +/** + * @} + */ diff --git a/src/def/par_def.h b/src/def/par_def.h new file mode 100644 index 0000000..b1bf927 --- /dev/null +++ b/src/def/par_def.h @@ -0,0 +1,231 @@ +/** + * @file par_def.h + * @brief Declare parameter-definition types and compile-time enumerations. + * @author wdfk-prog + * @version 1.0 + * @date 2026-03-27 + * + * @copyright Copyright (c) 2026 Ziga Miklosic. Distributed under the MIT license. + * + * @note : + * @par Change Log: + * Date Version Author Description + * 2026-03-27 1.0 wdfk-prog first version + */ +/** + * @addtogroup PAR_DEF + * @{ + */ + +#ifndef _PAR_DEF_CORE_H_ +#define _PAR_DEF_CORE_H_ + +#ifdef __cplusplus +extern "C" { +#endif +/** + * @brief Include dependencies. + */ +#include +#include +/** + * @brief Compile-time definitions. + * + * @note is required here because par_table.def uses true/false in + * the persistence column, and this header expands that column in + * PAR_PERSISTENT_COMPILE_COUNT. + */ +typedef struct par_cfg_s par_cfg_t; +/** + * @brief Centralized argument extractors for par_table.def X-Macro rows. + * @details Keep row-signature changes localized here. Consumers can use + * variadic wrappers plus these selectors instead of repeating the full row + * signature in every macro definition. + */ +#define PAR_XARG_ENUM(enum_, ...) enum_ +#define PAR_XARG_ID(_enum, id_, ...) id_ +#define PAR_XARG_NAME(_enum, _id, name_, ...) name_ +#define PAR_XARG_MIN(_enum, _id, _name, min_, ...) min_ +#define PAR_XARG_MAX(_enum, _id, _name, _min, max_, ...) max_ +#define PAR_XARG_DEF(_enum, _id, _name, _min, _max, def_, ...) def_ +#define PAR_XARG_UNIT(_enum, _id, _name, _min, _max, _def, unit_, ...) unit_ +#define PAR_XARG_ACCESS(_enum, _id, _name, _min, _max, _def, _unit, access_, ...) access_ +#define PAR_XARG_READ_ROLES(_enum, _id, _name, _min, _max, _def, _unit, _access, read_roles_, ...) read_roles_ +#define PAR_XARG_WRITE_ROLES(_enum, _id, _name, _min, _max, _def, _unit, _access, _read_roles, write_roles_, ...) write_roles_ +#define PAR_XARG_PERS(_enum, _id, _name, _min, _max, _def, _unit, _access, _read_roles, _write_roles, pers_, ...) pers_ +#define PAR_XARG_DESC(_enum, _id, _name, _min, _max, _def, _unit, _access, _read_roles, _write_roles, _pers, desc_) desc_ +/** + * @brief List of device parameters. + * @note Must be started with 0! @note Enum expansion is intentionally configuration-independent: PAR_ITEM_F32 always maps to PAR_ITEM_ENUM. F32 enable/disable fail-fast is enforced in par_def.c. + */ +#define PAR_ITEM_ENUM(...) PAR_XARG_ENUM(__VA_ARGS__), +enum +{ +#define PAR_ITEM_U8 PAR_ITEM_ENUM +#define PAR_ITEM_U16 PAR_ITEM_ENUM +#define PAR_ITEM_U32 PAR_ITEM_ENUM +#define PAR_ITEM_I8 PAR_ITEM_ENUM +#define PAR_ITEM_I16 PAR_ITEM_ENUM +#define PAR_ITEM_I32 PAR_ITEM_ENUM +#define PAR_ITEM_F32 PAR_ITEM_ENUM +#include "../../par_table.def" +#undef PAR_ITEM_U8 +#undef PAR_ITEM_U16 +#undef PAR_ITEM_U32 +#undef PAR_ITEM_I8 +#undef PAR_ITEM_I16 +#undef PAR_ITEM_I32 +#undef PAR_ITEM_F32 + + ePAR_NUM_OF +}; +#undef PAR_ITEM_ENUM +typedef uint16_t par_num_t; + +/** + * @brief Sentinel used by par_cfg_t.persist_idx for non-persistent parameters. + */ +#define PAR_PERSIST_IDX_INVALID UINT16_MAX +/** + * @brief Compile-time storage group counts derived from par_table.def. + * @note These constants are used by layout and static storage allocation. + */ +#define PAR_ITEM_COUNT_ONE(...) +1u +#define PAR_ITEM_COUNT_ZERO(...) +0u + +enum +{ + PAR_LAYOUT_COMPILE_COUNT8 = 0u +#define PAR_ITEM_U8 PAR_ITEM_COUNT_ONE +#define PAR_ITEM_U16 PAR_ITEM_COUNT_ZERO +#define PAR_ITEM_U32 PAR_ITEM_COUNT_ZERO +#define PAR_ITEM_I8 PAR_ITEM_COUNT_ONE +#define PAR_ITEM_I16 PAR_ITEM_COUNT_ZERO +#define PAR_ITEM_I32 PAR_ITEM_COUNT_ZERO +#define PAR_ITEM_F32 PAR_ITEM_COUNT_ZERO +#include "../../par_table.def" +#undef PAR_ITEM_U8 +#undef PAR_ITEM_U16 +#undef PAR_ITEM_U32 +#undef PAR_ITEM_I8 +#undef PAR_ITEM_I16 +#undef PAR_ITEM_I32 +#undef PAR_ITEM_F32 +}; + +enum +{ + PAR_LAYOUT_COMPILE_COUNT16 = 0u +#define PAR_ITEM_U8 PAR_ITEM_COUNT_ZERO +#define PAR_ITEM_U16 PAR_ITEM_COUNT_ONE +#define PAR_ITEM_U32 PAR_ITEM_COUNT_ZERO +#define PAR_ITEM_I8 PAR_ITEM_COUNT_ZERO +#define PAR_ITEM_I16 PAR_ITEM_COUNT_ONE +#define PAR_ITEM_I32 PAR_ITEM_COUNT_ZERO +#define PAR_ITEM_F32 PAR_ITEM_COUNT_ZERO +#include "../../par_table.def" +#undef PAR_ITEM_U8 +#undef PAR_ITEM_U16 +#undef PAR_ITEM_U32 +#undef PAR_ITEM_I8 +#undef PAR_ITEM_I16 +#undef PAR_ITEM_I32 +#undef PAR_ITEM_F32 +}; + +enum +{ + PAR_LAYOUT_COMPILE_COUNT32 = 0u +#define PAR_ITEM_U8 PAR_ITEM_COUNT_ZERO +#define PAR_ITEM_U16 PAR_ITEM_COUNT_ZERO +#define PAR_ITEM_U32 PAR_ITEM_COUNT_ONE +#define PAR_ITEM_I8 PAR_ITEM_COUNT_ZERO +#define PAR_ITEM_I16 PAR_ITEM_COUNT_ZERO +#define PAR_ITEM_I32 PAR_ITEM_COUNT_ONE +#define PAR_ITEM_F32 PAR_ITEM_COUNT_ONE +#include "../../par_table.def" +#undef PAR_ITEM_U8 +#undef PAR_ITEM_U16 +#undef PAR_ITEM_U32 +#undef PAR_ITEM_I8 +#undef PAR_ITEM_I16 +#undef PAR_ITEM_I32 +#undef PAR_ITEM_F32 +}; + +enum +{ + PAR_LAYOUT_COMPILE_COUNT_SUM = (PAR_LAYOUT_COMPILE_COUNT8 + PAR_LAYOUT_COMPILE_COUNT16 + PAR_LAYOUT_COMPILE_COUNT32) +}; + +/** + * @brief Compile-time persistent-slot enumeration derived from par_table.def. + * @details Only entries flagged with pers_ == true contribute a slot. The + * resulting PAR_PERSIST_IDX_ constants are dense and ordered exactly as + * the source table. + */ +#define PAR_PERSIST_ENUM_SELECT(enum_, pers_) PAR_PERSIST_ENUM_SELECT_I(enum_, pers_) +#define PAR_PERSIST_ENUM_SELECT_I(enum_, pers_) PAR_PERSIST_ENUM_SELECT_##pers_(enum_) +#define PAR_PERSIST_ENUM_SELECT_1(enum_) PAR_PERSIST_IDX_##enum_, +#define PAR_PERSIST_ENUM_SELECT_0(enum_) +#define PAR_ITEM_PERSIST_ENUM(...) PAR_PERSIST_ENUM_SELECT(PAR_XARG_ENUM(__VA_ARGS__), PAR_XARG_PERS(__VA_ARGS__)) +enum +{ +#define PAR_ITEM_U8 PAR_ITEM_PERSIST_ENUM +#define PAR_ITEM_U16 PAR_ITEM_PERSIST_ENUM +#define PAR_ITEM_U32 PAR_ITEM_PERSIST_ENUM +#define PAR_ITEM_I8 PAR_ITEM_PERSIST_ENUM +#define PAR_ITEM_I16 PAR_ITEM_PERSIST_ENUM +#define PAR_ITEM_I32 PAR_ITEM_PERSIST_ENUM +#define PAR_ITEM_F32 PAR_ITEM_PERSIST_ENUM +#include "../../par_table.def" +#undef PAR_ITEM_U8 +#undef PAR_ITEM_U16 +#undef PAR_ITEM_U32 +#undef PAR_ITEM_I8 +#undef PAR_ITEM_I16 +#undef PAR_ITEM_I32 +#undef PAR_ITEM_F32 + + PAR_PERSISTENT_COMPILE_COUNT, + PAR_PERSIST_SLOT_MAP_CAPACITY = (PAR_PERSISTENT_COMPILE_COUNT > 0U) ? PAR_PERSISTENT_COMPILE_COUNT : 1U +}; +#undef PAR_ITEM_PERSIST_ENUM +#undef PAR_PERSIST_ENUM_SELECT +#undef PAR_PERSIST_ENUM_SELECT_I +#undef PAR_PERSIST_ENUM_SELECT_1 +#undef PAR_PERSIST_ENUM_SELECT_0 + +#undef PAR_ITEM_COUNT_ONE +#undef PAR_ITEM_COUNT_ZERO +/** + * @brief Function declarations. + */ +const par_cfg_t *par_cfg_get_table(void); +const par_cfg_t *par_cfg_get(const par_num_t par_num); +/** + * @brief Return the compile-time parameter ID for one entry. + * + * @details This accessor is configuration-independent and remains available + * even when PAR_CFG_ENABLE_ID disables the runtime metadata field inside + * par_cfg_t. + * + * @param par_num Parameter number. + * @return Parameter ID from par_table.def. + */ +uint16_t par_cfg_get_param_id_const(const par_num_t par_num); +/** + * @brief Return the number of configuration entries. + * @return Configuration table size. + */ +uint32_t par_cfg_get_table_size(void); + + +#ifdef __cplusplus +} +#endif +/** + * @} + */ + +#endif /* _PAR_DEF_CORE_H_ */ diff --git a/src/def/par_id_map_static.c b/src/def/par_id_map_static.c new file mode 100644 index 0000000..00a5d07 --- /dev/null +++ b/src/def/par_id_map_static.c @@ -0,0 +1,42 @@ +/** + * @file par_id_map_static.c + * @brief Compile-time generated ID lookup map. + * @author wdfk-prog + * @version 1.0 + * @date 2026-03-24 + * + * @copyright Copyright (c) 2026 Ziga Miklosic. Distributed under the MIT license. + * + * @note : + * @par Change Log: + * Date Version Author Description + * 2026-03-24 1.0 wdfk-prog first version + */ +#include "def/par_id_map_static.h" + +#if (1 == PAR_CFG_ENABLE_ID) + +#define PAR_ID_MAP_ITEM(...) \ + [PAR_HASH_ID_CONST(PAR_XARG_ID(__VA_ARGS__))] = { .id = (uint16_t)(PAR_XARG_ID(__VA_ARGS__)), .par_num = (PAR_XARG_ENUM(__VA_ARGS__)), .used = 1u }, + +const par_id_map_entry_t g_par_id_map_static[PAR_ID_HASH_SIZE] = { +#define PAR_ITEM_U8 PAR_ID_MAP_ITEM +#define PAR_ITEM_U16 PAR_ID_MAP_ITEM +#define PAR_ITEM_U32 PAR_ID_MAP_ITEM +#define PAR_ITEM_I8 PAR_ID_MAP_ITEM +#define PAR_ITEM_I16 PAR_ID_MAP_ITEM +#define PAR_ITEM_I32 PAR_ID_MAP_ITEM +#define PAR_ITEM_F32 PAR_ID_MAP_ITEM +#include "../../par_table.def" +#undef PAR_ITEM_U8 +#undef PAR_ITEM_U16 +#undef PAR_ITEM_U32 +#undef PAR_ITEM_I8 +#undef PAR_ITEM_I16 +#undef PAR_ITEM_I32 +#undef PAR_ITEM_F32 +}; + +#undef PAR_ID_MAP_ITEM + +#endif diff --git a/src/def/par_id_map_static.h b/src/def/par_id_map_static.h new file mode 100644 index 0000000..30f9062 --- /dev/null +++ b/src/def/par_id_map_static.h @@ -0,0 +1,31 @@ +/** + * @file par_id_map_static.h + * @brief Declare the compile-time generated static ID lookup map. + * @author wdfk-prog + * @version 1.0 + * @date 2026-03-24 + * + * @copyright Copyright (c) 2026 Ziga Miklosic. Distributed under the MIT license. + * + * @note : + * @par Change Log: + * Date Version Author Description + * 2026-03-24 1.0 wdfk-prog first version + */ +#ifndef _PAR_ID_MAP_STATIC_H_ +#define _PAR_ID_MAP_STATIC_H_ + +#include "par.h" + +#if (1 == PAR_CFG_ENABLE_ID) +typedef struct +{ + uint16_t id; + par_num_t par_num; + uint8_t used; +} par_id_map_entry_t; + +extern const par_id_map_entry_t g_par_id_map_static[PAR_ID_HASH_SIZE]; +#endif + +#endif /* _PAR_ID_MAP_STATIC_H_ */ diff --git a/src/detail/par_bitwise_impl.inc b/src/detail/par_bitwise_impl.inc new file mode 100644 index 0000000..0412e10 --- /dev/null +++ b/src/detail/par_bitwise_impl.inc @@ -0,0 +1,51 @@ +/** + * @file par_bitwise_impl.inc + * @brief Generate bitwise fast setter implementations. + * @author wdfk-prog + * @version 1.0 + * @date 2026-03-27 + * + * @copyright Copyright (c) 2026 Ziga Miklosic. Distributed under the MIT license. + * + * @note : + * @par Change Log: + * Date Version Author Description + * 2026-03-27 1.0 wdfk-prog first version + */ +/** + * @brief Private include fragment. Included only by par.c. + */ + +#define PAR_FOR_EACH_BITWISE_OP(M, ...) \ + M(bitand, AND, __VA_ARGS__) \ + M(bitor, OR, __VA_ARGS__) +#define PAR_BITWISE_FAST_SET_BODY(ATOMIC_OP, NAME, FIELD, STORAGE, par_num, val) \ + do \ + { \ + PAR_ATOMIC_FETCH_##ATOMIC_OP(NAME, &(STORAGE)[g_par_offset[par_num]], (val)); \ + return ePAR_OK; \ + } while (0) + +#define PAR_IMPL_BITWISE_FAST_SETTER(OP_NAME, ATOMIC_OP, NAME, CTYPE, ETYPE, FIELD, STORAGE) \ + par_status_t par_##OP_NAME##_set_##NAME##_fast(const par_num_t par_num, const CTYPE val) \ + { \ + par_assert_fast_typed_preconditions(par_num, ETYPE); \ + PAR_BITWISE_FAST_SET_BODY(ATOMIC_OP, NAME, FIELD, STORAGE, par_num, val); \ + } + +#define PAR_BITWISE_ROWS(OP) \ + OP(u8, uint8_t, ePAR_TYPE_U8, u8, gpu8_par_value) \ + OP(u16, uint16_t, ePAR_TYPE_U16, u16, gpu16_par_value) \ + OP(u32, uint32_t, ePAR_TYPE_U32, u32, gpu32_par_value) + +#define PAR_IMPL_BITWISE_WIDTH(NAME, CTYPE, ETYPE, FIELD, STORAGE) \ + PAR_FOR_EACH_BITWISE_OP(PAR_IMPL_BITWISE_FAST_SETTER, NAME, CTYPE, ETYPE, FIELD, STORAGE) + +PAR_BITWISE_ROWS(PAR_IMPL_BITWISE_WIDTH) + +#undef PAR_IMPL_BITWISE_WIDTH +#undef PAR_BITWISE_ROWS + +#undef PAR_IMPL_BITWISE_FAST_SETTER +#undef PAR_BITWISE_FAST_SET_BODY +#undef PAR_FOR_EACH_BITWISE_OP diff --git a/src/detail/par_storage_init.inc b/src/detail/par_storage_init.inc new file mode 100644 index 0000000..3c05dd4 --- /dev/null +++ b/src/detail/par_storage_init.inc @@ -0,0 +1,101 @@ +/** + * @file par_storage_init.inc + * @brief Generate parameter storage initialization code. + * @author wdfk-prog + * @version 1.0 + * @date 2026-03-27 + * + * @copyright Copyright (c) 2026 Ziga Miklosic. Distributed under the MIT license. + * + * @note : + * @par Change Log: + * Date Version Author Description + * 2026-03-27 1.0 wdfk-prog first version + */ +/** + * @brief Private include fragment. Included only by par.c. + * @details Expands compile-time default initializers for the grouped live storage. + * object and preserves the internal U8/U16/U32 width-group layout. + */ + +#define PAR_STORAGE_INIT_NOP(...) +#define PAR_STORAGE_U8_FROM_U8(...) (par_atomic_u8_t)((uint8_t)(PAR_XARG_DEF(__VA_ARGS__))), +#define PAR_STORAGE_U8_FROM_I8(...) (par_atomic_u8_t)((uint8_t)(int8_t)(PAR_XARG_DEF(__VA_ARGS__))), +#define PAR_STORAGE_U16_FROM_U16(...) (par_atomic_u16_t)((uint16_t)(PAR_XARG_DEF(__VA_ARGS__))), +#define PAR_STORAGE_U16_FROM_I16(...) (par_atomic_u16_t)((uint16_t)(int16_t)(PAR_XARG_DEF(__VA_ARGS__))), +#define PAR_STORAGE_U32_FROM_U32(...) (par_atomic_u32_t)((uint32_t)(PAR_XARG_DEF(__VA_ARGS__))), +#define PAR_STORAGE_U32_FROM_I32(...) (par_atomic_u32_t)((uint32_t)(int32_t)(PAR_XARG_DEF(__VA_ARGS__))), +#if (1 == PAR_CFG_ENABLE_TYPE_F32) +#define PAR_STORAGE_U32_FROM_F32(...) (par_atomic_u32_t)(0u), +#endif + +static par_storage_groups_t gs_par_storage = { + .u8 = { +#define PAR_ITEM_U8 PAR_STORAGE_U8_FROM_U8 +#define PAR_ITEM_U16 PAR_STORAGE_INIT_NOP +#define PAR_ITEM_U32 PAR_STORAGE_INIT_NOP +#define PAR_ITEM_I8 PAR_STORAGE_U8_FROM_I8 +#define PAR_ITEM_I16 PAR_STORAGE_INIT_NOP +#define PAR_ITEM_I32 PAR_STORAGE_INIT_NOP +#define PAR_ITEM_F32 PAR_STORAGE_INIT_NOP +#include "../../par_table.def" +#undef PAR_ITEM_U8 +#undef PAR_ITEM_U16 +#undef PAR_ITEM_U32 +#undef PAR_ITEM_I8 +#undef PAR_ITEM_I16 +#undef PAR_ITEM_I32 +#undef PAR_ITEM_F32 + }, + + .u16 = { +#define PAR_ITEM_U8 PAR_STORAGE_INIT_NOP +#define PAR_ITEM_U16 PAR_STORAGE_U16_FROM_U16 +#define PAR_ITEM_U32 PAR_STORAGE_INIT_NOP +#define PAR_ITEM_I8 PAR_STORAGE_INIT_NOP +#define PAR_ITEM_I16 PAR_STORAGE_U16_FROM_I16 +#define PAR_ITEM_I32 PAR_STORAGE_INIT_NOP +#define PAR_ITEM_F32 PAR_STORAGE_INIT_NOP +#include "../../par_table.def" +#undef PAR_ITEM_U8 +#undef PAR_ITEM_U16 +#undef PAR_ITEM_U32 +#undef PAR_ITEM_I8 +#undef PAR_ITEM_I16 +#undef PAR_ITEM_I32 +#undef PAR_ITEM_F32 + }, + + .u32 = { +#define PAR_ITEM_U8 PAR_STORAGE_INIT_NOP +#define PAR_ITEM_U16 PAR_STORAGE_INIT_NOP +#define PAR_ITEM_U32 PAR_STORAGE_U32_FROM_U32 +#define PAR_ITEM_I8 PAR_STORAGE_INIT_NOP +#define PAR_ITEM_I16 PAR_STORAGE_INIT_NOP +#define PAR_ITEM_I32 PAR_STORAGE_U32_FROM_I32 +#if (1 == PAR_CFG_ENABLE_TYPE_F32) +#define PAR_ITEM_F32 PAR_STORAGE_U32_FROM_F32 +#else +#define PAR_ITEM_F32 PAR_STORAGE_INIT_NOP +#endif +#include "../../par_table.def" +#undef PAR_ITEM_U8 +#undef PAR_ITEM_U16 +#undef PAR_ITEM_U32 +#undef PAR_ITEM_I8 +#undef PAR_ITEM_I16 +#undef PAR_ITEM_I32 +#undef PAR_ITEM_F32 + } +}; + +#undef PAR_STORAGE_INIT_NOP +#undef PAR_STORAGE_U8_FROM_U8 +#undef PAR_STORAGE_U8_FROM_I8 +#undef PAR_STORAGE_U16_FROM_U16 +#undef PAR_STORAGE_U16_FROM_I16 +#undef PAR_STORAGE_U32_FROM_U32 +#undef PAR_STORAGE_U32_FROM_I32 +#if (1 == PAR_CFG_ENABLE_TYPE_F32) +#undef PAR_STORAGE_U32_FROM_F32 +#endif diff --git a/src/detail/par_typed_impl.inc b/src/detail/par_typed_impl.inc new file mode 100644 index 0000000..06e6713 --- /dev/null +++ b/src/detail/par_typed_impl.inc @@ -0,0 +1,373 @@ +/** + * @file par_typed_impl.inc + * @brief Generate typed getter and setter implementations. + * @author wdfk-prog + * @version 1.0 + * @date 2026-03-27 + * + * @copyright Copyright (c) 2026 Ziga Miklosic. Distributed under the MIT license. + * + * @note : + * @par Change Log: + * Date Version Author Description + * 2026-03-27 1.0 wdfk-prog first version + */ +/** + * @brief Macro-generated typed parameter APIs and checked write core. + * @details This fragment is included only by par.c and expands the typed setter/getter. + * families from a single type table. + * Generated API groups: + * par_set_u8/i8/u16/i16/u32/i32(/f32). + * par_set_u8_fast/i8_fast/u16_fast/i16_fast/u32_fast/i32_fast(/f32_fast). + * par_get_u8/i8/u16/i16/u32/i32(/f32). + */ + +#define PAR_TYPED_ROWS_BASE(X) \ + X(u8, U8, uint8_t, ePAR_TYPE_U8, u8) \ + X(i8, I8, int8_t, ePAR_TYPE_I8, i8) \ + X(u16, U16, uint16_t, ePAR_TYPE_U16, u16) \ + X(i16, I16, int16_t, ePAR_TYPE_I16, i16) \ + X(u32, U32, uint32_t, ePAR_TYPE_U32, u32) \ + X(i32, I32, int32_t, ePAR_TYPE_I32, i32) + +#if (1 == PAR_CFG_ENABLE_TYPE_F32) +#define PAR_TYPED_ROWS_F32(X) \ + X(f32, F32, float32_t, ePAR_TYPE_F32, f32) +#else +#define PAR_TYPED_ROWS_F32(X) +#endif + +#define PAR_TYPED_ROWS(X) \ + PAR_TYPED_ROWS_BASE(X) \ + PAR_TYPED_ROWS_F32(X) + +#if (1 == PAR_CFG_ENABLE_RANGE) +#define PAR_TYPED_FAST_SET_BODY(PRIV, FIELD, par_num, val) \ + do \ + { \ + const par_range_t range = par_get_range(par_num); \ + if ((val) > range.max.FIELD) \ + { \ + PAR_SET_##PRIV##_PRIV((par_num), range.max.FIELD); \ + PAR_WARN_PRINT("PAR: value limited to configured maximum, par_num=%u", \ + (unsigned)(par_num)); \ + return ePAR_WAR_LIMITED; \ + } \ + else if ((val) < range.min.FIELD) \ + { \ + PAR_SET_##PRIV##_PRIV((par_num), range.min.FIELD); \ + PAR_WARN_PRINT("PAR: value limited to configured minimum, par_num=%u", \ + (unsigned)(par_num)); \ + return ePAR_WAR_LIMITED; \ + } \ + else \ + { \ + PAR_SET_##PRIV##_PRIV((par_num), (val)); \ + return ePAR_OK; \ + } \ + } while (0) +#else +#define PAR_TYPED_FAST_SET_BODY(PRIV, FIELD, par_num, val) \ + do \ + { \ + PAR_SET_##PRIV##_PRIV((par_num), (val)); \ + return ePAR_OK; \ + } while (0) +#endif + +#define PAR_CHECKED_VALUE_CHANGED_u8(new_val, old_val) ((new_val).u8 != (old_val).u8) +#define PAR_CHECKED_VALUE_CHANGED_i8(new_val, old_val) ((new_val).i8 != (old_val).i8) +#define PAR_CHECKED_VALUE_CHANGED_u16(new_val, old_val) ((new_val).u16 != (old_val).u16) +#define PAR_CHECKED_VALUE_CHANGED_i16(new_val, old_val) ((new_val).i16 != (old_val).i16) +#define PAR_CHECKED_VALUE_CHANGED_u32(new_val, old_val) ((new_val).u32 != (old_val).u32) +#define PAR_CHECKED_VALUE_CHANGED_i32(new_val, old_val) ((new_val).i32 != (old_val).i32) +#define PAR_CHECKED_VALUE_CHANGED_f32(new_val, old_val) (!par_f32_bits_equal((new_val).f32, (old_val).f32)) +#define PAR_CHECKED_VALUE_CHANGED(FIELD, new_val, old_val) PAR_CHECKED_VALUE_CHANGED_##FIELD((new_val), (old_val)) + +#if (1 == PAR_CFG_ENABLE_CHANGE_CALLBACK) +#define PAR_CHECKED_OLD_VAL_DECL(PRIV, FIELD, par_num) \ + const par_type_t old_val = { .FIELD = PAR_GET_##PRIV##_PRIV(par_num) }; +#define PAR_CHECKED_ON_CHANGE(PRIV, FIELD, par_num, status_var) \ + do \ + { \ + pf_par_on_change_cb_t on_change = g_par_cb_table[(par_num)].on_change; \ + if (((ePAR_OK == (status_var)) || (ePAR_WAR_LIMITED == (status_var))) && \ + (NULL != on_change)) \ + { \ + const par_type_t new_val = { .FIELD = PAR_GET_##PRIV##_PRIV(par_num) }; \ + if (PAR_CHECKED_VALUE_CHANGED(FIELD, new_val, old_val)) \ + { \ + on_change((par_num), new_val, old_val); \ + } \ + } \ + } while (0) +#else +#define PAR_CHECKED_OLD_VAL_DECL(PRIV, FIELD, par_num) +#define PAR_CHECKED_ON_CHANGE(PRIV, FIELD, par_num, status_var) \ + do \ + { \ + (void)(par_num); \ + (void)(status_var); \ + } while (0) +#endif + +#if (1 == PAR_CFG_ENABLE_RUNTIME_VALIDATION) +#define PAR_CHECKED_VALIDATE_OR_SET(NAME, FIELD, par_num, val, status_var) \ + do \ + { \ + pf_par_validation_t validation = g_par_cb_table[(par_num)].validation; \ + if ((NULL != validation) && (false == validation((par_num), (val)))) \ + { \ + (status_var) = ePAR_ERROR_VALUE; \ + } \ + else \ + { \ + (status_var) = par_set_##NAME##_fast((par_num), (val).FIELD); \ + } \ + } while (0) +#else +#define PAR_CHECKED_VALIDATE_OR_SET(NAME, FIELD, par_num, val, status_var) \ + do \ + { \ + (status_var) = par_set_##NAME##_fast((par_num), (val).FIELD); \ + } while (0) +#endif + +#define PAR_CHECKED_LOAD_FROM_PTR_CASE(ETYPE, CTYPE, FIELD) \ + case ETYPE: \ + val.FIELD = *(const CTYPE *)p_ptr_val; \ + break; + +#define PAR_CHECKED_SET_CASE(NAME, PRIV, ETYPE, FIELD) \ + case ETYPE: \ + { \ + PAR_CHECKED_OLD_VAL_DECL(PRIV, FIELD, par_num); \ + PAR_CHECKED_VALIDATE_OR_SET(NAME, FIELD, par_num, val, status); \ + PAR_CHECKED_ON_CHANGE(PRIV, FIELD, par_num, status); \ + break; \ + } +/** + * @brief Assert fast typed setter preconditions. + * + * @note Fast typed APIs are internal trusted paths. They keep assert. + * based precondition checks only and intentionally avoid the. + * public metadata getter path. + * + * @param par_num Parameter number (enumeration). + * @param expected_type Expected parameter type for operation. + */ +static void par_assert_fast_typed_preconditions(const par_num_t par_num, + const par_type_list_t expected_type) +{ + const par_cfg_t *p_cfg = NULL; + + PAR_ASSERT(true == par_is_init()); + PAR_ASSERT(par_num < ePAR_NUM_OF); + + if ((true == par_is_init()) && (par_num < ePAR_NUM_OF)) + { + p_cfg = par_get_config(par_num); + PAR_ASSERT(NULL != p_cfg); + + if (NULL != p_cfg) + { + PAR_ASSERT(expected_type == p_cfg->type); + } + } +} +/** + * @brief Validate expected parameter type against resolved metadata. + * + * @note Callers shall resolve metadata first and then validate the. + * expected parameter type against the resolved configuration. + * + * @param p_cfg Resolved parameter configuration entry. + * @param expected_type Expected parameter type for operation. + * @return Status of operation. + */ +static par_status_t par_validate_expected_type(const par_cfg_t * const p_cfg, + const par_type_list_t expected_type) +{ + if (NULL == p_cfg) + { + return ePAR_ERROR; + } + + PAR_ASSERT(expected_type == p_cfg->type); + if (expected_type != p_cfg->type) + { + PAR_ERR_PRINT("PAR: type mismatch, expected=%u actual=%u", + (unsigned)expected_type, + (unsigned)p_cfg->type); + return ePAR_ERROR_TYPE; + } + + return ePAR_OK; +} +/** + * @brief Checked parameter write core for all public setter APIs. + * + * @note This is the single enforcement point for all public write. + * paths. It performs runtime access checks, type validation,. + * access enforcement, optional runtime validation, fast setter. + * dispatch, and optional on-change callback dispatch. + * + * @note Exactly one value source shall be provided: + * - p_typed_val: used by typed setters par_set_xxx(). + * - p_ptr_val : used by generic par_set(). + * + * @note Unchecked internal paths such as par_set_xxx_fast(), NVM. + * restore helpers, and other explicit fast paths do not call. + * this function. + * + * @param par_num Parameter number (enumeration). + * @param expected_type Expected parameter type for write. + * @param p_typed_val Optional typed union value source. + * @param p_ptr_val Optional pointer value source. + * + * @return Status of operation. + */ +static par_status_t par_set_checked_core(const par_num_t par_num, const par_type_list_t expected_type, const par_type_t * const p_typed_val, const void * const p_ptr_val) +{ + const par_cfg_t *par_cfg = NULL; + par_type_t val = { 0 }; + par_status_t status = par_resolve_runtime(par_num, p_ptr_val, (NULL == p_typed_val), &par_cfg); + + if (ePAR_OK != status) + { + return status; + } + + status = par_validate_expected_type(par_cfg, expected_type); + if (ePAR_OK != status) + { + return status; + } + +#if (1 == PAR_CFG_ENABLE_ACCESS) + if (false == par_access_has_write(par_cfg->access)) + { + PAR_INFO_PRINT("PAR: write denied by access capability, par_num=%u", (unsigned)par_num); + return ePAR_ERROR_ACCESS; + } +#endif + + if (NULL != p_typed_val) + { + val = *p_typed_val; + } + else + { + switch (expected_type) + { + PAR_CHECKED_LOAD_FROM_PTR_CASE(ePAR_TYPE_U8, uint8_t, u8) + PAR_CHECKED_LOAD_FROM_PTR_CASE(ePAR_TYPE_I8, int8_t, i8) + PAR_CHECKED_LOAD_FROM_PTR_CASE(ePAR_TYPE_U16, uint16_t, u16) + PAR_CHECKED_LOAD_FROM_PTR_CASE(ePAR_TYPE_I16, int16_t, i16) + PAR_CHECKED_LOAD_FROM_PTR_CASE(ePAR_TYPE_U32, uint32_t, u32) + PAR_CHECKED_LOAD_FROM_PTR_CASE(ePAR_TYPE_I32, int32_t, i32) +#if (1 == PAR_CFG_ENABLE_TYPE_F32) + PAR_CHECKED_LOAD_FROM_PTR_CASE(ePAR_TYPE_F32, float32_t, f32) +#endif + case ePAR_TYPE_NUM_OF: + default: + PAR_ASSERT(0); + return ePAR_ERROR_TYPE; + } + } + + if (ePAR_OK != par_acquire_mutex(par_num)) + { + return ePAR_ERROR_MUTEX; + } + + switch (expected_type) + { + PAR_CHECKED_SET_CASE(u8, U8, ePAR_TYPE_U8, u8) + PAR_CHECKED_SET_CASE(i8, I8, ePAR_TYPE_I8, i8) + PAR_CHECKED_SET_CASE(u16, U16, ePAR_TYPE_U16, u16) + PAR_CHECKED_SET_CASE(i16, I16, ePAR_TYPE_I16, i16) + PAR_CHECKED_SET_CASE(u32, U32, ePAR_TYPE_U32, u32) + PAR_CHECKED_SET_CASE(i32, I32, ePAR_TYPE_I32, i32) +#if (1 == PAR_CFG_ENABLE_TYPE_F32) + PAR_CHECKED_SET_CASE(f32, F32, ePAR_TYPE_F32, f32) +#endif + case ePAR_TYPE_NUM_OF: + default: + PAR_ASSERT(0); + status = ePAR_ERROR_TYPE; + break; + } + + par_release_mutex(par_num); + return status; +} + +/** + * @brief Generate normal typed setters: par_set_xxx(). + */ +#define PAR_TYPED_IMPL_SETTER(NAME, PRIV, CTYPE, ETYPE, FIELD) \ + par_status_t par_set_##NAME(const par_num_t par_num, const CTYPE val) \ + { \ + const par_type_t typed_val = { .FIELD = val }; \ + return par_set_checked_core(par_num, ETYPE, &typed_val, NULL); \ + } + +PAR_TYPED_ROWS(PAR_TYPED_IMPL_SETTER) +#undef PAR_TYPED_IMPL_SETTER + +/** + * @brief Generate fast typed setters: par_set_xxx_fast(). + */ +#define PAR_TYPED_IMPL_FAST_SETTER(NAME, PRIV, CTYPE, ETYPE, FIELD) \ + par_status_t par_set_##NAME##_fast(const par_num_t par_num, const CTYPE val) \ + { \ + par_assert_fast_typed_preconditions(par_num, ETYPE); \ + PAR_TYPED_FAST_SET_BODY(PRIV, FIELD, par_num, val); \ + } + +PAR_TYPED_ROWS(PAR_TYPED_IMPL_FAST_SETTER) +#undef PAR_TYPED_IMPL_FAST_SETTER + +/** + * @brief Generate typed getters: par_get_xxx(). + */ +#define PAR_TYPED_IMPL_GETTER(NAME, PRIV, CTYPE, ETYPE, FIELD) \ + par_status_t par_get_##NAME(const par_num_t par_num, CTYPE * const p_val) \ + { \ + const par_cfg_t *par_cfg = NULL; \ + par_status_t status = par_resolve_runtime(par_num, p_val, true, &par_cfg); \ + if (ePAR_OK != status) \ + return status; \ + status = par_validate_expected_type(par_cfg, ETYPE); \ + if (ePAR_OK != status) \ + return status; \ +#if (1 == PAR_CFG_ENABLE_ACCESS) \ + if (false == par_access_has_read(par_cfg->access)) \ + return ePAR_ERROR_ACCESS; \ +#endif \ + *p_val = PAR_GET_##PRIV##_PRIV(par_num); \ + return ePAR_OK; \ + } + +PAR_TYPED_ROWS(PAR_TYPED_IMPL_GETTER) +#undef PAR_TYPED_IMPL_GETTER + +#undef PAR_CHECKED_SET_CASE +#undef PAR_CHECKED_LOAD_FROM_PTR_CASE +#undef PAR_CHECKED_VALIDATE_OR_SET +#undef PAR_CHECKED_ON_CHANGE +#undef PAR_CHECKED_OLD_VAL_DECL +#undef PAR_CHECKED_VALUE_CHANGED +#undef PAR_CHECKED_VALUE_CHANGED_f32 +#undef PAR_CHECKED_VALUE_CHANGED_i32 +#undef PAR_CHECKED_VALUE_CHANGED_u32 +#undef PAR_CHECKED_VALUE_CHANGED_i16 +#undef PAR_CHECKED_VALUE_CHANGED_u16 +#undef PAR_CHECKED_VALUE_CHANGED_i8 +#undef PAR_CHECKED_VALUE_CHANGED_u8 + +#undef PAR_TYPED_FAST_SET_BODY +#undef PAR_TYPED_ROWS +#undef PAR_TYPED_ROWS_F32 +#undef PAR_TYPED_ROWS_BASE diff --git a/src/layout/par_layout.c b/src/layout/par_layout.c new file mode 100644 index 0000000..e3be847 --- /dev/null +++ b/src/layout/par_layout.c @@ -0,0 +1,146 @@ +/** + * @file par_layout.c + * @brief Implement parameter storage layout helpers. + * @author wdfk-prog + * @version 1.0 + * @date 2026-03-27 + * + * @copyright Copyright (c) 2026 Ziga Miklosic. Distributed under the MIT license. + * + * @note : + * @par Change Log: + * Date Version Author Description + * 2026-03-27 1.0 wdfk-prog first version + */ +/** + * @addtogroup PAR_LAYOUT + * @{ + */ +/** + * @brief Include dependencies. + */ +#include "layout/par_layout.h" +#include "par.h" +/** + * @brief Compile-time definitions. + */ +PAR_STATIC_ASSERT(par_layout_max_par_num_fit_u16, (ePAR_NUM_OF <= PAR_UINT16_MAX)); +PAR_STATIC_ASSERT(par_layout_compile_count_sum_match, ((uint32_t)PAR_LAYOUT_COMPILE_COUNT_SUM == (uint32_t)ePAR_NUM_OF)); + +static const par_layout_count_t gs_layout_count = { + .count8 = (uint16_t)PAR_STORAGE_COUNT8, + .count16 = (uint16_t)PAR_STORAGE_COUNT16, + .count32 = (uint16_t)PAR_STORAGE_COUNT32, +}; +/** + * @brief Runtime-generated offset table. + * @note Offsets are generated with a plain runtime scan loop instead of macro. expansion to keep the mapping logic readable and easy to debug. + * @note When PAR_CFG_LAYOUT_SOURCE is PAR_CFG_LAYOUT_SCRIPT, runtime fill is. compile-time disabled and PAR_LAYOUT_STATIC_OFFSET_TABLE is consumed. directly, avoiding extra maintenance complexity. + */ +static uint16_t gs_runtime_offset[ePAR_NUM_OF] = { 0u }; +static const uint16_t *gsp_active_offset = gs_runtime_offset; + +#if (PAR_CFG_LAYOUT_SOURCE == PAR_CFG_LAYOUT_SCRIPT) +#ifndef PAR_LAYOUT_STATIC_OFFSET_TABLE +#error "PAR_LAYOUT_STATIC_OFFSET_TABLE must be provided by static layout include!" +#endif +#endif +/** + * @brief Function declarations and definitions. + */ +/** + * @brief Initialize active parameter layout source. + * + * @note COMPILE_SCAN mode fills runtime offset table. + * SCRIPT mode directly consumes static script table. + */ +void par_layout_init(void) +{ +#if (PAR_CFG_LAYOUT_SOURCE == PAR_CFG_LAYOUT_COMPILE_SCAN) + par_layout_count_t scan_count = { 0u, 0u, 0u }; + gsp_active_offset = gs_runtime_offset; + + for (uint32_t par_it = 0u; par_it < (uint32_t)ePAR_NUM_OF; par_it++) + { + const par_cfg_t * const p_cfg = par_cfg_get((par_num_t)par_it); + switch (p_cfg->type) + { + case ePAR_TYPE_U8: + case ePAR_TYPE_I8: + gs_runtime_offset[par_it] = scan_count.count8; + scan_count.count8++; + break; + + case ePAR_TYPE_U16: + case ePAR_TYPE_I16: + gs_runtime_offset[par_it] = scan_count.count16; + scan_count.count16++; + break; + + case ePAR_TYPE_U32: + case ePAR_TYPE_I32: +#if (1 == PAR_CFG_ENABLE_TYPE_F32) + case ePAR_TYPE_F32: +#endif + gs_runtime_offset[par_it] = scan_count.count32; + scan_count.count32++; + break; + + case ePAR_TYPE_NUM_OF: + default: + PAR_ERR_PRINT("PAR: layout encountered unsupported type at par_num=%u", (unsigned)par_it); + PAR_ASSERT(0); + return; + } + } + + if ((scan_count.count8 != gs_layout_count.count8) || (scan_count.count16 != gs_layout_count.count16) || (scan_count.count32 != gs_layout_count.count32)) + { + PAR_ERR_PRINT("PAR: layout count mismatch, scan=(%u,%u,%u) cfg=(%u,%u,%u)", + (unsigned)scan_count.count8, + (unsigned)scan_count.count16, + (unsigned)scan_count.count32, + (unsigned)gs_layout_count.count8, + (unsigned)gs_layout_count.count16, + (unsigned)gs_layout_count.count32); + PAR_ASSERT(0); + return; + } +#else + /* Script layout mode: consume provided static layout directly with no runtime validation. */ + gsp_active_offset = PAR_LAYOUT_STATIC_OFFSET_TABLE; + PAR_DBG_PRINT("PAR: layout initialized from generated static table"); + return; +#endif +} +/** + * @brief Get active offset table pointer. + * + * @return Pointer to active offset table. + */ +const uint16_t *par_layout_get_offset_table(void) +{ + return gsp_active_offset; +} +/** + * @brief Get offset by parameter number. + * + * @param par_num Parameter number (enumeration). + * @return Offset inside type group storage. + */ +uint16_t par_layout_get_offset(const par_num_t par_num) +{ + return gsp_active_offset[par_num]; +} +/** + * @brief Get per-width layout counts. + * + * @return Number of 8/16/32-bit parameters. + */ +par_layout_count_t par_layout_get_count(void) +{ + return gs_layout_count; +} +/** + * @} + */ diff --git a/src/layout/par_layout.h b/src/layout/par_layout.h new file mode 100644 index 0000000..b1a5964 --- /dev/null +++ b/src/layout/par_layout.h @@ -0,0 +1,80 @@ +/** + * @file par_layout.h + * @brief Declare parameter storage layout helpers. + * @author wdfk-prog + * @version 1.0 + * @date 2026-03-27 + * + * @copyright Copyright (c) 2026 Ziga Miklosic. Distributed under the MIT license. + * + * @note : + * @par Change Log: + * Date Version Author Description + * 2026-03-27 1.0 wdfk-prog first version + */ +#ifndef _PAR_LAYOUT_H_ +#define _PAR_LAYOUT_H_ +/** + * @brief Include dependencies. + */ +#include + +#include "par_cfg.h" +#include "def/par_def.h" + +#ifdef __cplusplus +extern "C" { +#endif +/** + * @brief Compile-time definitions. + */ +typedef struct +{ + uint16_t count8; + uint16_t count16; + uint16_t count32; +} par_layout_count_t; + +#if (PAR_CFG_LAYOUT_SOURCE == PAR_CFG_LAYOUT_COMPILE_SCAN) +#define PAR_STORAGE_COUNT8 (PAR_LAYOUT_COMPILE_COUNT8) +#define PAR_STORAGE_COUNT16 (PAR_LAYOUT_COMPILE_COUNT16) +#define PAR_STORAGE_COUNT32 (PAR_LAYOUT_COMPILE_COUNT32) +#elif (PAR_CFG_LAYOUT_SOURCE == PAR_CFG_LAYOUT_SCRIPT) +#include PAR_CFG_LAYOUT_STATIC_INCLUDE +#define PAR_STORAGE_COUNT8 (PAR_LAYOUT_STATIC_COUNT8) +#define PAR_STORAGE_COUNT16 (PAR_LAYOUT_STATIC_COUNT16) +#define PAR_STORAGE_COUNT32 (PAR_LAYOUT_STATIC_COUNT32) +#else +#error "Unsupported PAR_CFG_LAYOUT_SOURCE value!" +#endif + +#define PAR_STORAGE_NONZERO(count_) (((count_) > 0u) ? (count_) : 1u) +/** + * @brief Function declarations and definitions. + */ +/** + * @brief Return the parameter offset table. + * @return Pointer to the offset table. + */ +const uint16_t *par_layout_get_offset_table(void); +/** + * @brief Return the storage offset for one parameter. + * @param par_num Parameter number. + * @return Storage offset for par_num. + */ +uint16_t par_layout_get_offset(const par_num_t par_num); +/** + * @brief Return the grouped storage counts. + * @return Grouped storage counts for 8-bit, 16-bit, and 32-bit data. + */ +par_layout_count_t par_layout_get_count(void); +/** + * @brief Initialize the storage layout metadata. + */ +void par_layout_init(void); + +#ifdef __cplusplus +} +#endif + +#endif /* _PAR_LAYOUT_H_ */ diff --git a/src/par.c b/src/par.c index e3f00e2..a124944 100644 --- a/src/par.c +++ b/src/par.c @@ -1,2201 +1,1691 @@ -// Copyright (c) 2026 Ziga Miklosic -// All Rights Reserved -// This software is under MIT licence (https://opensource.org/licenses/MIT) -//////////////////////////////////////////////////////////////////////////////// -/** -*@file par.c -*@brief Device parameters API functions -*@author Ziga Miklosic -*@email ziga.miklosic@gmail.com -*@author Matej Otic -*@email otic.matej@dancing-bits.com -*@date 29.01.2026 -*@version V3.0.1 -*/ -//////////////////////////////////////////////////////////////////////////////// -/** -*@addtogroup PARAMETERS_API -* @{ -*/ -//////////////////////////////////////////////////////////////////////////////// - -//////////////////////////////////////////////////////////////////////////////// -// Includes -//////////////////////////////////////////////////////////////////////////////// -#include +/** + * @file par.c + * @brief Implement the public device-parameter API. + * @author Ziga Miklosic + * @version V3.0.1 + * @date 2026-01-29 + * + * @copyright Copyright (c) 2026 Ziga Miklosic. Distributed under the MIT license. + * + * @note : + * @par Change Log: + * Date Version Author Description + * 2026-01-29 V3.0.1 Ziga Miklosic first version + */ +/** + * @addtogroup PARAMETERS_API + * @{ + */ +/** + * @brief Include dependencies. + */ #include #include -#include #include "par.h" -#include "par_nvm.h" -#include "../../par_if.h" - -//////////////////////////////////////////////////////////////////////////////// -// Definitions -//////////////////////////////////////////////////////////////////////////////// - -//////////////////////////////////////////////////////////////////////////////// -// Variables -//////////////////////////////////////////////////////////////////////////////// - +#include "port/par_atomic.h" +#include "layout/par_layout.h" +#include "def/par_id_map_static.h" +#include "persist/par_nvm.h" +#include "port/par_if.h" +/** + * @brief Compile-time definitions. + */ +PAR_STATIC_ASSERT(par_atomic_u8_i8_same_size, sizeof(par_atomic_u8_t) == sizeof(par_atomic_i8_t)); +PAR_STATIC_ASSERT(par_atomic_u8_i8_same_align, PAR_ALIGNOF(par_atomic_u8_t) == PAR_ALIGNOF(par_atomic_i8_t)); +PAR_STATIC_ASSERT(par_atomic_u16_i16_same_size, sizeof(par_atomic_u16_t) == sizeof(par_atomic_i16_t)); +PAR_STATIC_ASSERT(par_atomic_u16_i16_same_align, PAR_ALIGNOF(par_atomic_u16_t) == PAR_ALIGNOF(par_atomic_i16_t)); +PAR_STATIC_ASSERT(par_atomic_u32_i32_same_size, sizeof(par_atomic_u32_t) == sizeof(par_atomic_i32_t)); +PAR_STATIC_ASSERT(par_atomic_u32_i32_same_align, PAR_ALIGNOF(par_atomic_u32_t) == PAR_ALIGNOF(par_atomic_i32_t)); +PAR_STATIC_ASSERT(par_atomic_u32_f32_same_size, sizeof(par_atomic_u32_t) == sizeof(par_atomic_f32_t)); +PAR_STATIC_ASSERT(par_atomic_u32_f32_same_align, PAR_ALIGNOF(par_atomic_u32_t) == PAR_ALIGNOF(par_atomic_f32_t)); +/** + * @brief Module-scope variables. + */ /** - * Initialization guard + * @brief Initialization guard. */ static bool gb_is_init = false; - /** - * Parameter callback table. + * @brief Parameter callback table. + * @note Keep runtime hooks separate from par_cfg_t metadata table. */ +#if ((1 == PAR_CFG_ENABLE_RUNTIME_VALIDATION) || (1 == PAR_CFG_ENABLE_CHANGE_CALLBACK)) static struct { - pf_par_validation_t validation; /**< Validation callback function (or NULL). */ - pf_par_on_change_cb_t on_change; /**< On change callback function (or NULL). */ +#if (1 == PAR_CFG_ENABLE_RUNTIME_VALIDATION) + pf_par_validation_t validation; /**< Validation callback function (or NULL). */ +#endif +#if (1 == PAR_CFG_ENABLE_CHANGE_CALLBACK) + pf_par_on_change_cb_t on_change; /**< On change callback function (or NULL). */ +#endif } g_par_cb_table[ePAR_NUM_OF]; +#endif /** - * Parameter live values divided by its type in RAM + * @brief Grouped typed storage backing parameter live values. + * + * @note Storage is organized as U8/U16/U32 typed members inside one private. + * grouped storage object. + * + * @note Zero-length groups are mapped to size 1 arrays for compiler portability. + * + * @note Private implementation fragment below must not be included outside par.c. */ -static _Atomic uint8_t * gpu8_par_value = NULL; -static _Atomic int8_t * gpi8_par_value = NULL; -static _Atomic uint16_t * gpu16_par_value = NULL; -static _Atomic int16_t * gpi16_par_value = NULL; -static _Atomic uint32_t * gpu32_par_value = NULL; -static _Atomic int32_t * gpi32_par_value = NULL; -static _Atomic float32_t * gpf32_par_value = NULL; +typedef struct +{ + par_atomic_u8_t u8[PAR_STORAGE_NONZERO(PAR_STORAGE_COUNT8)]; + par_atomic_u16_t u16[PAR_STORAGE_NONZERO(PAR_STORAGE_COUNT16)]; + par_atomic_u32_t u32[PAR_STORAGE_NONZERO(PAR_STORAGE_COUNT32)]; +} par_storage_groups_t; +/** + * @brief Private implementation fragment. Do not include outside par.c. + * @details Defines gs_par_storage with grouped typed initializers. + */ +#include "detail/par_storage_init.inc" +#if (1 == PAR_CFG_ENABLE_RESET_ALL_RAW) /** - * Address offset by parameter enumeration + * @brief Runtime grouped default mirror storage for raw reset-all API. + * + * @note Mirrors are initialized in par_init() from current live defaults. + * after F32 startup patch and before optional NVM load. + * + * @note Mirror layout matches the grouped live storage object. */ -static uint32_t gu32_par_offset[ ePAR_NUM_OF ] = { 0 }; +static par_storage_groups_t gs_par_default_mirror = { 0 }; +#endif /** - * Private getters and setters + * @brief Typed live-value access pointers into grouped storage. */ -#define PAR_GET_U8_PRIV(par_num) atomic_load_explicit( &gpu8_par_value[gu32_par_offset[par_num]], memory_order_relaxed ) -#define PAR_GET_I8_PRIV(par_num) atomic_load_explicit( &gpi8_par_value[gu32_par_offset[par_num]], memory_order_relaxed ) -#define PAR_GET_U16_PRIV(par_num) atomic_load_explicit( &gpu16_par_value[gu32_par_offset[par_num]], memory_order_relaxed ) -#define PAR_GET_I16_PRIV(par_num) atomic_load_explicit( &gpi16_par_value[gu32_par_offset[par_num]], memory_order_relaxed ) -#define PAR_GET_U32_PRIV(par_num) atomic_load_explicit( &gpu32_par_value[gu32_par_offset[par_num]], memory_order_relaxed ) -#define PAR_GET_I32_PRIV(par_num) atomic_load_explicit( &gpi32_par_value[gu32_par_offset[par_num]], memory_order_relaxed ) +static par_atomic_u8_t * const gpu8_par_value = gs_par_storage.u8; +static par_atomic_i8_t * const gpi8_par_value = (par_atomic_i8_t *)gs_par_storage.u8; +static par_atomic_u16_t * const gpu16_par_value = gs_par_storage.u16; +static par_atomic_i16_t * const gpi16_par_value = (par_atomic_i16_t *)gs_par_storage.u16; +static par_atomic_u32_t * const gpu32_par_value = gs_par_storage.u32; +static par_atomic_i32_t * const gpi32_par_value = (par_atomic_i32_t *)gs_par_storage.u32; +#if (1 == PAR_CFG_ENABLE_TYPE_F32) +static par_atomic_f32_t * const gpf32_par_value = (par_atomic_f32_t *)gs_par_storage.u32; +#endif -// NOTICE: "atomic_load_explicit" does not support float data type, therfore using GCC/CLang build-in primitive "__atomic_load" to overcome this limitation -#define PAR_GET_F32_PRIV(par_num) ({ \ - float32_t __val; \ - __atomic_load( &gpf32_par_value[gu32_par_offset[par_num]], &__val, __ATOMIC_RELAXED); \ - __val; \ -}) +/** + * @brief Offset table compatibility alias. + * + * @note Layout offsets are now owned by par_layout and accessed via getter. + * Keep this local alias so existing indexed access sites remain unchanged. + */ +#define g_par_offset (par_layout_get_offset_table()) -#define PAR_SET_U8_PRIV(par_num, val) atomic_store_explicit( &gpu8_par_value[gu32_par_offset[par_num]], val, memory_order_relaxed ) -#define PAR_SET_I8_PRIV(par_num, val) atomic_store_explicit( &gpi8_par_value[gu32_par_offset[par_num]], val, memory_order_relaxed ) -#define PAR_SET_U16_PRIV(par_num, val) atomic_store_explicit( &gpu16_par_value[gu32_par_offset[par_num]], val, memory_order_relaxed ) -#define PAR_SET_I16_PRIV(par_num, val) atomic_store_explicit( &gpi16_par_value[gu32_par_offset[par_num]], val, memory_order_relaxed ) -#define PAR_SET_U32_PRIV(par_num, val) atomic_store_explicit( &gpu32_par_value[gu32_par_offset[par_num]], val, memory_order_relaxed ) -#define PAR_SET_I32_PRIV(par_num, val) atomic_store_explicit( &gpi32_par_value[gu32_par_offset[par_num]], val, memory_order_relaxed ) +/** + * @brief Private getters and setters. + */ +#define PAR_GET_U8_PRIV(par_num) PAR_ATOMIC_LOAD(u8, &gpu8_par_value[g_par_offset[par_num]]) +#define PAR_GET_I8_PRIV(par_num) PAR_ATOMIC_LOAD(i8, &gpi8_par_value[g_par_offset[par_num]]) +#define PAR_GET_U16_PRIV(par_num) PAR_ATOMIC_LOAD(u16, &gpu16_par_value[g_par_offset[par_num]]) +#define PAR_GET_I16_PRIV(par_num) PAR_ATOMIC_LOAD(i16, &gpi16_par_value[g_par_offset[par_num]]) +#define PAR_GET_U32_PRIV(par_num) PAR_ATOMIC_LOAD(u32, &gpu32_par_value[g_par_offset[par_num]]) +#define PAR_GET_I32_PRIV(par_num) PAR_ATOMIC_LOAD(i32, &gpi32_par_value[g_par_offset[par_num]]) +#if (1 == PAR_CFG_ENABLE_TYPE_F32) +#define PAR_GET_F32_PRIV(par_num) PAR_ATOMIC_LOAD(f32, &gpf32_par_value[g_par_offset[par_num]]) +#endif -// NOTICE: "atomic_store_explicit" does not support float data type, therfore using GCC/CLang build-in primitive "__atomic_store" to overcome this limitation -#define PAR_SET_F32_PRIV(par_num, val) __atomic_store( &gpf32_par_value[gu32_par_offset[par_num]], &val, memory_order_relaxed ) +#define PAR_SET_U8_PRIV(par_num, val) PAR_ATOMIC_STORE(u8, &gpu8_par_value[g_par_offset[par_num]], (val)) +#define PAR_SET_I8_PRIV(par_num, val) PAR_ATOMIC_STORE(i8, &gpi8_par_value[g_par_offset[par_num]], (val)) +#define PAR_SET_U16_PRIV(par_num, val) PAR_ATOMIC_STORE(u16, &gpu16_par_value[g_par_offset[par_num]], (val)) +#define PAR_SET_I16_PRIV(par_num, val) PAR_ATOMIC_STORE(i16, &gpi16_par_value[g_par_offset[par_num]], (val)) +#define PAR_SET_U32_PRIV(par_num, val) PAR_ATOMIC_STORE(u32, &gpu32_par_value[g_par_offset[par_num]], (val)) +#define PAR_SET_I32_PRIV(par_num, val) PAR_ATOMIC_STORE(i32, &gpi32_par_value[g_par_offset[par_num]], (val)) +#if (1 == PAR_CFG_ENABLE_TYPE_F32) +#define PAR_SET_F32_PRIV(par_num, val) PAR_ATOMIC_STORE(f32, &gpf32_par_value[g_par_offset[par_num]], (val)) +#endif -#if ( PAR_CFG_DEBUG_EN ) +#if (PAR_CFG_DEBUG_EN) - /** - * Status strings - */ - static const char * gs_status[] = - { - "OK", +/** + * @brief Status strings. + */ +static const char *gs_status[] = { + "OK", + + "ERROR", + "ERROR INIT", + "ERROR NVM", + "ERROR CRC", + "ERROR TYPE", + "ERROR MUTEX", + "ERROR VALUE", + "ERROR PARAM", + "ERROR PAR NUM", + "ERROR ACCESS", + "ERROR TABLE ID", + "WARN SET TO DEF", + "WARN NVM REWRITTEN", + "NO PERSISTENT", + "LIMITED", + "N/A", +}; +#endif +/** + * @brief Function declarations. + */ +static par_status_t par_set_checked_core(const par_num_t par_num, const par_type_list_t expected_type, const par_type_t * const p_typed_val, const void * const p_ptr_val); +#if (1 == PAR_CFG_ENABLE_ACCESS) +static bool par_access_has_read(const par_access_t access); +static bool par_access_has_write(const par_access_t access); +#endif +#if (1 == PAR_CFG_ENABLE_ROLE_POLICY) +static bool par_roles_are_valid(const par_role_t roles); +#endif - "ERROR", - "ERROR INIT", - "ERROR NVM", - "ERROR CRC", - "ERROR TYPE", - "ERROR MUTEX", - "ERROR_VALUE", +/** + * @brief Function declarations and definitions. + */ +#if (1 == PAR_CFG_ENABLE_ACCESS) +/** + * @brief Return whether the access mask contains read capability. + */ +static bool par_access_has_read(const par_access_t access) +{ + return (0U != ((uint32_t)access & (uint32_t)ePAR_ACCESS_READ)); +} - "WARN SET TO DEF", - "WARN NVM REWRITTEN", - "NO PERSISTENT", - "LIMITED", - }; +/** + * @brief Return whether the access mask contains write capability. + */ +static bool par_access_has_write(const par_access_t access) +{ + return (0U != ((uint32_t)access & (uint32_t)ePAR_ACCESS_WRITE)); +} #endif - -//////////////////////////////////////////////////////////////////////////////// -// Function Prototypes -//////////////////////////////////////////////////////////////////////////////// -static void par_allocate_ram_space (void); -static par_status_t par_check_table_validy (const par_cfg_t * const p_par_cfg); -static bool par_is_value_changed (const par_num_t par_num, const void * p_val); - -//////////////////////////////////////////////////////////////////////////////// -// Functions -//////////////////////////////////////////////////////////////////////////////// - -//////////////////////////////////////////////////////////////////////////////// -/** -* Allocate space for live parameter values -* -* @return Pointer to allocated RAM space for parameter values -*/ -//////////////////////////////////////////////////////////////////////////////// -static void par_allocate_ram_space(void) +#if (1 == PAR_CFG_ENABLE_ROLE_POLICY) +/** + * @brief Validate that a role mask contains only supported role bits. + */ +static bool par_roles_are_valid(const par_role_t roles) { - uint32_t total_size = 0; - void * mem = NULL; + return (0U == ((uint32_t)roles & (uint32_t)(~((uint32_t)ePAR_ROLE_ALL)))); +} +#endif +#if (1 == PAR_CFG_ENABLE_DESC) && (1 == PAR_CFG_ENABLE_DESC_CHECK) +/** + * @brief Validate parameter description string. + * + * @note Default weak implementation only prohibits comma character. + * Application may override this symbol with stronger policy. + * + * @param p_desc Parameter description. + * @return true if description is valid. + */ +PAR_PORT_WEAK bool par_port_is_desc_valid(const char * const p_desc) +{ + return ((NULL == p_desc) || (NULL == strchr(p_desc, ','))); +} +#endif +/** + * @brief Resolve metadata entry for parameter identified by number. + * + * @note Metadata resolution only checks parameter number and table. + * entry. It intentionally does not require the runtime module. + * to be initialized, because callers only read compile-time. + * metadata. + * + * @param par_num Parameter number (enumeration). + * @param p_arg Optional pointer argument to validate. + * @param require_arg True if p_arg must not be NULL. + * @param pp_cfg Optional output pointer to parameter configuration. + * @return Status of operation. + */ +static par_status_t par_resolve_metadata(const par_num_t par_num, const void * const p_arg, const bool require_arg, const par_cfg_t ** const pp_cfg) +{ + const par_cfg_t *p_cfg = NULL; - // Group 32-bit types first - Alignment safety - uint32_t group32_size = 0, group32_count = 0; - for ( par_num_t par_it = 0; par_it < ePAR_NUM_OF; par_it++ ) + if ((true == require_arg) && (NULL == p_arg)) { - if ( ( ePAR_TYPE_U32 == par_get_type(par_it)) - || ( ePAR_TYPE_I32 == par_get_type(par_it)) - || ( ePAR_TYPE_F32 == par_get_type(par_it))) - { - gu32_par_offset[par_it] = group32_count; - group32_size += 4; - group32_count++; - } + return ePAR_ERROR_PARAM; } - // Group 16-bit types second - uint32_t group16_size = 0, group16_count = 0; - for ( par_num_t par_it = 0; par_it < ePAR_NUM_OF; par_it++ ) + PAR_ASSERT(par_num < ePAR_NUM_OF); + if (par_num >= ePAR_NUM_OF) { - if ( ( ePAR_TYPE_U16 == par_get_type(par_it)) - || ( ePAR_TYPE_I16 == par_get_type(par_it))) - { - gu32_par_offset[par_it] = group16_count; - group16_size += 2; - group16_count++; - } + return ePAR_ERROR_PAR_NUM; } - // Group 8-bit types last - uint32_t group8_size = 0, group8_count = 0; - for ( par_num_t par_it = 0; par_it < ePAR_NUM_OF; par_it++ ) + p_cfg = par_get_config(par_num); + if (NULL == p_cfg) { - if ( ( ePAR_TYPE_U8 == par_get_type(par_it)) - || ( ePAR_TYPE_I8 == par_get_type(par_it))) - { - gu32_par_offset[par_it] = group8_count; - group8_size += 1; - group8_count++; - } + return ePAR_ERROR; } - // Calculate full RAM size and allocate memory in single shot - total_size = group32_size + group16_size + group8_size; - mem = malloc(total_size); + if (NULL != pp_cfg) + { + *pp_cfg = p_cfg; + } + + return ePAR_OK; +} +/** + * @brief Resolve runtime metadata entry for parameter identified by number. + * + * @note Runtime resolution extends metadata resolution with module. + * init state validation, because live parameter storage is only. + * valid after successful par_init(). + * + * @param par_num Parameter number (enumeration). + * @param p_arg Optional pointer argument to validate. + * @param require_arg True if p_arg must not be NULL. + * @param pp_cfg Optional output pointer to parameter configuration. + * @return Status of operation. + */ +static par_status_t par_resolve_runtime(const par_num_t par_num, const void * const p_arg, const bool require_arg, const par_cfg_t ** const pp_cfg) +{ + if (true != par_is_init()) + { + return ePAR_ERROR_INIT; + } - // 32-bit vars share the first part of the memory - gpu32_par_value = mem; - gpf32_par_value = mem; - gpi32_par_value = mem; + return par_resolve_metadata(par_num, p_arg, require_arg, pp_cfg); +} - // 16-bit vars share the middle part of the memory - gpu16_par_value = mem + group32_size; - gpi16_par_value = mem + group32_size; +/** + * @brief Compare two F32 values by raw bit pattern. + * + * @note This helper intentionally uses memcpy() instead of pointer. + * casting or union type-punning. memcpy() preserves the exact. + * IEEE-754 bit pattern while remaining strict-aliasing safe. + * Bitwise comparison keeps NaN payloads and signed-zero handling. + * deterministic for parameter storage use cases. + * + * @param lhs Left-hand float value. + * @param rhs Right-hand float value. + * @return true if raw 32-bit representations are equal. + */ +static bool par_f32_bits_equal(const float32_t lhs, const float32_t rhs) +{ + uint32_t lhs_bits = 0U; + uint32_t rhs_bits = 0U; - // 8-bit vars share the last part of the memory - gpu8_par_value = mem + group32_size + group16_size; - gpi8_par_value = mem + group32_size + group16_size; + memcpy(&lhs_bits, &lhs, sizeof(lhs_bits)); + memcpy(&rhs_bits, &rhs, sizeof(rhs_bits)); - PAR_DBG_PRINT( "Total RAM consumption for parameters value: %d bytes", total_size ); + return (lhs_bits == rhs_bits); } -//////////////////////////////////////////////////////////////////////////////// +#if (1 == PAR_CFG_ENABLE_TYPE_F32) /** -* Check that parameter table is correctly defined -* -* @param[in] p_par_cfg - Pointer to parameters table -* @return status - Status of operation -*/ -//////////////////////////////////////////////////////////////////////////////// -static par_status_t par_check_table_validy(const par_cfg_t * const p_par_cfg) + * @brief Patch F32 defaults into shared u32 storage as bit-patterns. + * + * @note Integer defaults are already initialized at definition time. + * F32 defaults are patched once after layout offsets are available. + */ +static void par_patch_f32_defaults_from_table(void) { - par_status_t status = ePAR_OK; + for (par_num_t par_num = 0; par_num < ePAR_NUM_OF; par_num++) + { + const par_cfg_t * const p_cfg = par_cfg_get(par_num); + if (ePAR_TYPE_F32 == p_cfg->type) + { + PAR_SET_F32_PRIV(par_num, p_cfg->def.f32); + } + } +} +#endif +/** + * @brief Bind static space for live parameter values. + */ +static void par_bind_storage_layout(void) +{ + const par_layout_count_t layout_count = par_layout_get_count(); + + par_layout_init(); + PAR_DBG_PRINT("PAR: layout bound, count8=%u count16=%u count32=%u", + (unsigned)layout_count.count8, + (unsigned)layout_count.count16, + (unsigned)layout_count.count32); + PAR_DBG_PRINT("PAR: total RAM consumption for parameter values: %u bytes", + (unsigned)(((uint32_t)layout_count.count32 * 4u) + + ((uint32_t)layout_count.count16 * 2u) + + ((uint32_t)layout_count.count8))); +} +/** + * @brief Hash parameter ID to bucket index. + * + * @param id Parameter ID. + * @return hash index. + */ +#if (1 == PAR_CFG_ENABLE_ID) +static inline uint32_t par_hash_id(const uint16_t id) +{ + return PAR_HASH_ID_CONST(id); +} + +#if ((1 == PAR_CFG_ENABLE_RUNTIME_ID_DUP_CHECK) || (1 == PAR_CFG_ENABLE_RUNTIME_ID_HASH_COLLISION_CHECK)) +/** + * @brief Run optional runtime diagnostics on the compiled parameter ID table. + * + * @note Static ID-map generation and compile-time conflict checks are. + * always enabled when PAR_CFG_ENABLE_ID = 1. This function exists. + * only to provide runtime diagnostics and clearer conflict logs. + * + * @param p_par_cfg Pointer to parameters table. + * @return Status of operation. + */ +static par_status_t par_runtime_validate_id_table(const par_cfg_t * const p_par_cfg) +{ + par_id_map_entry_t diag_map[PAR_ID_HASH_SIZE]; + memset(diag_map, 0, sizeof(diag_map)); - // For each parameter - for ( uint32_t i = 0; i < ePAR_NUM_OF; i++ ) + for (par_num_t par_num = 0; par_num < ePAR_NUM_OF; par_num++) { - // Compare parameters IDs - for ( uint32_t j = 0; j < ePAR_NUM_OF; j++ ) + const uint16_t id = p_par_cfg[par_num].id; + const uint32_t bucket_idx = par_hash_id(id); + par_id_map_entry_t * const bucket = &diag_map[bucket_idx]; + + if (0u == bucket->used) { - if ( i != j ) - { - // Check for two identical IDs - if ( p_par_cfg[i].id == p_par_cfg[j].id ) - { - status = ePAR_ERROR_INIT; - PAR_DBG_PRINT( "ERR, Two parameters have the same ID %d!", p_par_cfg[i].id ); - PAR_ASSERT( 0 ); - break; - } - } + bucket->used = 1u; + bucket->id = id; + bucket->par_num = par_num; + continue; } - /** - * Check for correct MIN, MAX and DEF value definitions - * - * 1. Check that MAX is larger than MIN - * 2. Check that DEF is equal or less than MAX - * 3. Check that DEF is equal or more than MIN - */ - PAR_ASSERT(( ePAR_TYPE_U8 == p_par_cfg[i].type ) ? ((( p_par_cfg[i].min.u8 < p_par_cfg[i].max.u8 ) && ( p_par_cfg[i].def.u8 <= p_par_cfg[i].max.u8 )) && ( p_par_cfg[i].min.u8 <= p_par_cfg[i].def.u8 )) : ( 1 )); - PAR_ASSERT(( ePAR_TYPE_I8 == p_par_cfg[i].type ) ? ((( p_par_cfg[i].min.i8 < p_par_cfg[i].max.i8 ) && ( p_par_cfg[i].def.i8 <= p_par_cfg[i].max.i8 )) && ( p_par_cfg[i].min.i8 <= p_par_cfg[i].def.i8 )) : ( 1 )); - PAR_ASSERT(( ePAR_TYPE_U16 == p_par_cfg[i].type ) ? ((( p_par_cfg[i].min.u16 < p_par_cfg[i].max.u16 ) && ( p_par_cfg[i].def.u16 <= p_par_cfg[i].max.u16 )) && ( p_par_cfg[i].min.u16 <= p_par_cfg[i].def.u16 )) : ( 1 )); - PAR_ASSERT(( ePAR_TYPE_I16 == p_par_cfg[i].type ) ? ((( p_par_cfg[i].min.i16 < p_par_cfg[i].max.i16 ) && ( p_par_cfg[i].def.i16 <= p_par_cfg[i].max.i16 )) && ( p_par_cfg[i].min.i16 <= p_par_cfg[i].def.i16 )) : ( 1 )); - PAR_ASSERT(( ePAR_TYPE_U32 == p_par_cfg[i].type ) ? ((( p_par_cfg[i].min.u32 < p_par_cfg[i].max.u32 ) && ( p_par_cfg[i].def.u32 <= p_par_cfg[i].max.u32 )) && ( p_par_cfg[i].min.u32 <= p_par_cfg[i].def.u32 )) : ( 1 )); - PAR_ASSERT(( ePAR_TYPE_I32 == p_par_cfg[i].type ) ? ((( p_par_cfg[i].min.i32 < p_par_cfg[i].max.i32 ) && ( p_par_cfg[i].def.i32 <= p_par_cfg[i].max.i32 )) && ( p_par_cfg[i].min.i32 <= p_par_cfg[i].def.i32 )) : ( 1 )); - PAR_ASSERT(( ePAR_TYPE_F32 == p_par_cfg[i].type ) ? ((( p_par_cfg[i].min.f32 < p_par_cfg[i].max.f32 ) && ( p_par_cfg[i].def.f32 <= p_par_cfg[i].max.f32 )) && ( p_par_cfg[i].min.f32 <= p_par_cfg[i].def.f32 )) : ( 1 )); - - // Parameter shall have a description and the name - if (( NULL == p_par_cfg[i].name ) || ( NULL == p_par_cfg[i].desc )) +#if (1 == PAR_CFG_ENABLE_RUNTIME_ID_DUP_CHECK) + if (bucket->id == id) { - status = ePAR_ERROR_INIT; - PAR_DBG_PRINT( "ERR, Parameter %d definition incomplete!", i ); - PAR_ASSERT( 0 ); - break; + PAR_ERR_PRINT("PAR: duplicate parameter ID %u detected during runtime diagnostic scan", (unsigned)id); + PAR_ASSERT(0); + return ePAR_ERROR_INIT; } - else +#endif + +#if (1 == PAR_CFG_ENABLE_RUNTIME_ID_HASH_COLLISION_CHECK) + if (bucket->id != id) { - // ',' is prohibited in parameter description - // NOTE: ',' is used as column separator and will break PC tool side parser logic in case of usage in description! - if ( NULL != strchr(p_par_cfg[i].desc, ',')) - { - status = ePAR_ERROR_INIT; - PAR_DBG_PRINT( "ERR, Parameter %d description contains comma!", i ); - PAR_ASSERT( 0 ); - break; - } + PAR_ERR_PRINT("PAR: hash collision detected during runtime diagnostic scan, id=%u conflicts_with=%u bucket=%u", + (unsigned)id, + (unsigned)bucket->id, + (unsigned)bucket_idx); + PAR_ASSERT(0); + return ePAR_ERROR_INIT; } +#endif } - return status; + return ePAR_OK; } - -//////////////////////////////////////////////////////////////////////////////// +#endif +#endif /** -* Is parameter value changed -* -* @param[in] par_num - Parameter number -* @param[in] p_val - Parameter value -* @return True if parameter value is different from current -*/ -//////////////////////////////////////////////////////////////////////////////// -static bool par_is_value_changed(const par_num_t par_num, const void * p_val) + * @brief Check that parameter table is correctly defined. + * + * @param p_par_cfg Pointer to parameters table. + * @return Status of operation. + */ +static par_status_t par_check_table_validity(const par_cfg_t * const p_par_cfg) { - bool value_changed = false; + par_status_t status = ePAR_OK; - switch ( par_get_type(par_num)) +#if (1 == PAR_CFG_ENABLE_ID) && ((1 == PAR_CFG_ENABLE_RUNTIME_ID_DUP_CHECK) || (1 == PAR_CFG_ENABLE_RUNTIME_ID_HASH_COLLISION_CHECK)) + status = par_runtime_validate_id_table(p_par_cfg); + if (ePAR_OK != status) { - case ePAR_TYPE_U8: - value_changed = (par_get_u8(par_num) != *(uint8_t*)p_val); - break; - - case ePAR_TYPE_I8: - value_changed = (par_get_i8(par_num) != *(int8_t*)p_val); - break; - - case ePAR_TYPE_U16: - value_changed = (par_get_u16(par_num) != *(uint16_t*)p_val); - break; - - case ePAR_TYPE_I16: - value_changed = (par_get_i16(par_num) != *(int16_t*)p_val); - break; + return status; + } +#endif - case ePAR_TYPE_U32: - value_changed = (par_get_u32(par_num) != *(uint32_t*)p_val); - break; + for (uint32_t i = 0; i < ePAR_NUM_OF; i++) + { +#if (1 == PAR_CFG_ENABLE_RANGE) + /* + * Keep F32 range/default validation at runtime. + * Some embedded GCC variants do not treat these float comparisons as + * constant expressions in file-scope static assertions. + */ + PAR_ASSERT((ePAR_TYPE_F32 == p_par_cfg[i].type) ? (((p_par_cfg[i].range.min.f32 < p_par_cfg[i].range.max.f32) && + (p_par_cfg[i].def.f32 <= p_par_cfg[i].range.max.f32)) && + (p_par_cfg[i].range.min.f32 <= p_par_cfg[i].def.f32)) + : (1)); +#endif - case ePAR_TYPE_I32: - value_changed = (par_get_i32(par_num) != *(int32_t*)p_val); +#if (1 == PAR_CFG_ENABLE_NAME) + if (NULL == p_par_cfg[i].name) + { + status = ePAR_ERROR_INIT; + PAR_ERR_PRINT("PAR: parameter %u name missing", (unsigned)i); + PAR_ASSERT(0); break; + } +#endif - case ePAR_TYPE_F32: - value_changed = (par_get_f32(par_num) != *(float32_t*)p_val); +#if (1 == PAR_CFG_ENABLE_DESC) + if (NULL == p_par_cfg[i].desc) + { + status = ePAR_ERROR_INIT; + PAR_ERR_PRINT("PAR: parameter %u description missing", (unsigned)i); + PAR_ASSERT(0); break; + } +#endif - case ePAR_TYPE_NUM_OF: - default: - PAR_ASSERT( 0 ); +#if (1 == PAR_CFG_ENABLE_DESC) && (1 == PAR_CFG_ENABLE_DESC_CHECK) + if (false == par_port_is_desc_valid(p_par_cfg[i].desc)) + { + status = ePAR_ERROR_INIT; + PAR_ERR_PRINT("PAR: parameter %u description invalid", (unsigned)i); + PAR_ASSERT(0); break; + } +#endif } - return value_changed; + return status; } - -//////////////////////////////////////////////////////////////////////////////// /** -* @} -*/ -//////////////////////////////////////////////////////////////////////////////// + * @} + */ -//////////////////////////////////////////////////////////////////////////////// /** -*@addtogroup API_FUNCTIONS -* @{ -* -* Following function are part of Device Parameter module API. -*/ -//////////////////////////////////////////////////////////////////////////////// + * @addtogroup API_FUNCTIONS + * @{ + * + * @brief Following function are part of Device Parameter module API. + */ -//////////////////////////////////////////////////////////////////////////////// /** -* Device Parameters initialization -* -* At init parameter table is being check for correct definition, allocation -* in RAM space for parameters live values and additionaly interface to -* platform is being done. -* -* @return status - Status of initialization -*/ -//////////////////////////////////////////////////////////////////////////////// + * @brief Device Parameters initialization. + * + * At init parameter table is being check for correct definition, allocation. + * in RAM space for parameters live values and additionaly interface to. + * platform is being done. + * + * @return Status of initialization. + */ par_status_t par_init(void) { par_status_t status = ePAR_OK; - PAR_ASSERT( false == par_is_init()); - if ( false != par_is_init()) return ePAR_ERROR_INIT; + PAR_ASSERT(false == par_is_init()); + if (false != par_is_init()) + return ePAR_ERROR_INIT; - // Check if par table is defined correctly - status |= par_check_table_validy( par_cfg_get_table()); - - // Allocate space in RAM - par_allocate_ram_space(); - - // Initialize parameter interface + PAR_DBG_PRINT("PAR: initialization started"); + status |= par_check_table_validity(par_cfg_get_table()); + par_bind_storage_layout(); status |= par_if_init(); - - // Init succeed PAR_ASSERT(ePAR_OK == status); - if ( ePAR_OK == status ) + if (ePAR_OK == status) { gb_is_init = true; + /* Patch F32 defaults after layout offsets become available. */ +#if (1 == PAR_CFG_ENABLE_TYPE_F32) + par_patch_f32_defaults_from_table(); + PAR_DBG_PRINT("PAR: F32 defaults patched into 32-bit storage group"); +#endif - // Set all parameters to default - par_set_all_to_default(); +#if (1 == PAR_CFG_ENABLE_RESET_ALL_RAW) + /* Snapshot defaults before optional NVM restore. */ + memcpy(&gs_par_default_mirror, &gs_par_storage, sizeof(gs_par_storage)); + PAR_DBG_PRINT("PAR: default mirror snapshot captured for raw reset path"); +#endif - #if ( 1 == PAR_CFG_NVM_EN ) - // Init and load parameters from NVM - status |= par_nvm_init(); - #endif +#if (1 == PAR_CFG_NVM_EN) + /* Restore persisted values after default initialization. */ + PAR_DBG_PRINT("PAR: restoring persistent values from NVM"); + status |= par_nvm_init(); +#endif } - PAR_DBG_PRINT( "PAR: Parameters initialized with status: %s", par_get_status_str( status )); + PAR_INFO_PRINT("PAR: initialization finished with status=%s", par_get_status_str(status)); return status; } - -//////////////////////////////////////////////////////////////////////////////// /** -* De-initialize Device Parameters -* -* @return status - Status of de-initialization -*/ -//////////////////////////////////////////////////////////////////////////////// + * @brief De-initialize Device Parameters. + * + * @return Status of de-initialization. + */ par_status_t par_deinit(void) { par_status_t status = ePAR_OK; + par_status_t deinit_status = ePAR_OK; - PAR_ASSERT( true == par_is_init()); - if ( true != par_is_init()) return ePAR_ERROR_INIT; + PAR_ASSERT(true == par_is_init()); + if (true != par_is_init()) + return ePAR_ERROR_INIT; - #if ( 1 == PAR_CFG_NVM_EN ) - // Init and load parameters from NVM - status = par_nvm_deinit(); - #endif + PAR_DBG_PRINT("PAR: deinitialization started"); +#if (1 == PAR_CFG_NVM_EN) + deinit_status = par_nvm_deinit(); + status |= deinit_status; +#endif - // Module de-initialized + deinit_status = par_if_deinit(); + status |= deinit_status; gb_is_init = false; + PAR_INFO_PRINT("PAR: deinitialization finished with status=%s", par_get_status_str(status)); return status; } - -//////////////////////////////////////////////////////////////////////////////// /** -* Get initialization flag -* -* @return Initialization state -*/ -//////////////////////////////////////////////////////////////////////////////// + * @brief Get initialization flag. + * + * @return Initialization state. + */ bool par_is_init(void) { return gb_is_init; } - -//////////////////////////////////////////////////////////////////////////////// /** -* Try to acquire mutex for specified parameter -* -* @param[in] par_num - Parameter number (enumeration) -* @return status - Status of operation -*/ -//////////////////////////////////////////////////////////////////////////////// + * @brief Try to acquire mutex for specified parameter. + * + * @param par_num Parameter number (enumeration). + * @return Status of operation. + */ par_status_t par_acquire_mutex(const par_num_t par_num) { - PAR_ASSERT( par_num < ePAR_NUM_OF ); + PAR_ASSERT(par_num < ePAR_NUM_OF); return par_if_aquire_mutex(par_num); } - -//////////////////////////////////////////////////////////////////////////////// /** -* Try to acquire mutex for specified parameter -* -* @param[in] par_num - Parameter number (enumeration) -* @return void -*/ -//////////////////////////////////////////////////////////////////////////////// + * @brief Try to acquire mutex for specified parameter. + * + * @param par_num Parameter number (enumeration). + */ void par_release_mutex(const par_num_t par_num) { - PAR_ASSERT( par_num < ePAR_NUM_OF ); + PAR_ASSERT(par_num < ePAR_NUM_OF); par_if_release_mutex(par_num); } +/** + * @brief Set parameter value. + * + * @note Mandatory to cast input argument to appropriate type. E.g.: + * + * @code + * float32_t my_val = 1.234f;. + * par_set( ePAR_MY_VAR, (float32_t*) &my_val );. + * @endcode + * + * @note Input is the internal parameter number (`par_num_t`) from `par_def.h`, + * not the external parameter ID. + * + * @param par_num Parameter number (enumeration). + * @param p_val Pointer to value. + * @return Status of operation. + */ +par_status_t par_set(const par_num_t par_num, const void *p_val) +{ + const par_cfg_t *par_cfg = NULL; + const par_status_t status = par_resolve_runtime(par_num, p_val, true, &par_cfg); + + if (ePAR_OK != status) + { + return status; + } -//////////////////////////////////////////////////////////////////////////////// -/** -* Set parameter value -* -* @note Mandatory to cast input argument to appropriate type. E.g.: -* -* @code -* float32_t my_val = 1.234f; -* par_set( ePAR_MY_VAR, (float32_t*) &my_val ); -* @endcode -* -* @note Input is parameter number (enumeration) defined in par_cfg.h and not -* parameter ID number! -* -* @param[in] par_num - Parameter number (enumeration) -* @param[in] p_val - Pointer to value -* @return status - Status of operation -*/ -//////////////////////////////////////////////////////////////////////////////// -par_status_t par_set(const par_num_t par_num, const void * p_val) + return par_set_checked_core(par_num, par_cfg->type, NULL, p_val); +} +/** + * @brief Set parameter value through the generic unchecked fast path. + * + * @param par_num Parameter number (enumeration). + * @param p_val Pointer to value. + * @return Status of operation. + */ +par_status_t par_set_fast(const par_num_t par_num, const void *p_val) { - par_status_t status = ePAR_OK; + const par_cfg_t *par_cfg = NULL; + const par_status_t status = par_resolve_runtime(par_num, p_val, true, &par_cfg); - switch ( par_get_type(par_num)) + if (ePAR_OK != status) { - case ePAR_TYPE_U8: - status = par_set_u8( par_num, *(uint8_t*) p_val ); - break; + return status; + } - case ePAR_TYPE_I8: - status = par_set_i8( par_num, *(int8_t*) p_val ); - break; + switch (par_cfg->type) + { + case ePAR_TYPE_U8: + return par_set_u8_fast(par_num, *(const uint8_t *)p_val); - case ePAR_TYPE_U16: - status = par_set_u16( par_num, *(uint16_t*) p_val ); - break; + case ePAR_TYPE_I8: + return par_set_i8_fast(par_num, *(const int8_t *)p_val); - case ePAR_TYPE_I16: - status = par_set_i16( par_num, *(int16_t*) p_val ); - break; + case ePAR_TYPE_U16: + return par_set_u16_fast(par_num, *(const uint16_t *)p_val); - case ePAR_TYPE_U32: - status = par_set_u32( par_num, *(uint32_t*) p_val ); - break; + case ePAR_TYPE_I16: + return par_set_i16_fast(par_num, *(const int16_t *)p_val); - case ePAR_TYPE_I32: - status = par_set_i32( par_num, *(int32_t*) p_val ); - break; + case ePAR_TYPE_U32: + return par_set_u32_fast(par_num, *(const uint32_t *)p_val); - case ePAR_TYPE_F32: - status = par_set_f32( par_num, *(float32_t*) p_val ); - break; + case ePAR_TYPE_I32: + return par_set_i32_fast(par_num, *(const int32_t *)p_val); - case ePAR_TYPE_NUM_OF: - default: - PAR_ASSERT( 0 ); - return ePAR_ERROR_TYPE; - } +#if (1 == PAR_CFG_ENABLE_TYPE_F32) + case ePAR_TYPE_F32: + return par_set_f32_fast(par_num, *(const float32_t *)p_val); +#endif - return status; + case ePAR_TYPE_NUM_OF: + default: + PAR_ASSERT(0); + return ePAR_ERROR_TYPE; + } } - -//////////////////////////////////////////////////////////////////////////////// /** -* Set parameter value by ID -* -* @param[in] id - Parameter ID number -* @param[in] p_val - Pointer to value -* @return status - Status of operation -*/ -//////////////////////////////////////////////////////////////////////////////// -par_status_t par_set_by_id(const uint16_t id, const void * p_val) + * @brief Set parameter value by ID. + * + * @param id Parameter ID number. + * @param p_val Pointer to value. + * @return Status of operation. + */ +#if (1 == PAR_CFG_ENABLE_ID) +par_status_t par_set_by_id(const uint16_t id, const void *p_val) { - par_num_t par_num; + par_num_t par_num = 0U; + const par_status_t status = par_get_num_by_id(id, &par_num); - if ( ePAR_OK == par_get_num_by_id( id, &par_num )) - { - return par_set( par_num, p_val ); - } - else + if (ePAR_OK != status) { - return ePAR_ERROR; + return status; } -} -//////////////////////////////////////////////////////////////////////////////// + return par_set(par_num, p_val); +} +#endif +/** + * @brief Typed parameter API implementation expansion point. + * + * The following public APIs are not handwritten one by one in this file. + * They are emitted by macro expansion from "par_typed_impl.inc": + * + * - par_set_u8 / i8 / u16 / i16 / u32 / i32 (/ f32). + * - par_set_u8_fast / i8_fast / u16_fast / i16_fast / u32_fast / i32_fast (/ f32_fast). + * - par_get_u8 / i8 / u16 / i16 / u32 / i32 (/ f32). + * + * If IDE navigation cannot jump from par.h declarations to concrete bodies,. + * inspect this include point and then open par_typed_impl.inc. + * + * @note Private implementation fragment. Do not include outside par.c. + */ +#include "detail/par_typed_impl.inc" /** -* Set unsigned 8-bit parameter -* -* @param[in] par_num - Parameter number (enumeration) -* @param[in] val - Value of parameter -* @return status - Status of operation -*/ -//////////////////////////////////////////////////////////////////////////////// -par_status_t par_set_u8(const par_num_t par_num, const uint8_t val) + * @brief Bitwise fast setter implementation. + * @note Private implementation fragments. Do not include outside par.c. + */ +#include "detail/par_bitwise_impl.inc" +/** + * @brief Set parameter to default value. + * + * @pre Parameters must be initialised before usage! + * + * @param par_num Parameter number (enumeration). + * @return Status of operation. + */ +par_status_t par_set_to_default(const par_num_t par_num) { - par_status_t status = ePAR_OK; + const par_cfg_t *par_cfg = NULL; + par_status_t status = par_resolve_runtime(par_num, NULL, false, &par_cfg); - // Check initialization - PAR_ASSERT( true == par_is_init()); - if ( true != par_is_init()) return ePAR_ERROR_INIT; + if (ePAR_OK != status) + { + return status; + } - // Check for invalid type - PAR_ASSERT( ePAR_TYPE_U8 == par_get_type(par_num)); - if( ePAR_TYPE_U8 != par_get_type(par_num)) return ePAR_ERROR_TYPE; + if (ePAR_OK != par_acquire_mutex(par_num)) + { + return ePAR_ERROR_MUTEX; + } - pf_par_validation_t validation = g_par_cb_table[par_num].validation; - pf_par_on_change_cb_t on_change = g_par_cb_table[par_num].on_change; - // Get mutex - if ( ePAR_OK == par_acquire_mutex(par_num)) + switch (par_cfg->type) { - const par_type_t old_val = {.u8 = PAR_GET_U8_PRIV( par_num )}; - - // Validated parameter value - if ((validation == NULL) || validation(par_num, (par_type_t){.u8 = val})) - { - status = par_set_u8_fast( par_num, val ); - } - else - { - status = ePAR_ERROR_VALUE; - } + case ePAR_TYPE_U8: + status = par_set_u8_fast(par_num, par_cfg->def.u8); + break; - // Raise on change callback - const par_type_t new_val = {.u8 = PAR_GET_U8_PRIV( par_num )}; - if ((on_change != NULL) && (new_val.u8 != old_val.u8)) - { - on_change(par_num, new_val, old_val); - } + case ePAR_TYPE_I8: + status = par_set_i8_fast(par_num, par_cfg->def.i8); + break; + + case ePAR_TYPE_U16: + status = par_set_u16_fast(par_num, par_cfg->def.u16); + break; + + case ePAR_TYPE_I16: + status = par_set_i16_fast(par_num, par_cfg->def.i16); + break; + + case ePAR_TYPE_U32: + status = par_set_u32_fast(par_num, par_cfg->def.u32); + break; + + case ePAR_TYPE_I32: + status = par_set_i32_fast(par_num, par_cfg->def.i32); + break; + +#if (1 == PAR_CFG_ENABLE_TYPE_F32) + case ePAR_TYPE_F32: + status = par_set_f32_fast(par_num, par_cfg->def.f32); + break; +#endif + + case ePAR_TYPE_NUM_OF: + default: + PAR_ASSERT(0); + status = ePAR_ERROR_TYPE; + break; + } + + par_release_mutex(par_num); - par_release_mutex(par_num); + if (ePAR_OK == (status & ePAR_STATUS_ERROR_MASK)) + { + PAR_DBG_PRINT("PAR: restored default value, par_num=%u", (unsigned)par_num); } else { - status = ePAR_ERROR_MUTEX; + PAR_ERR_PRINT("PAR: failed to restore default value, par_num=%u status=%s", (unsigned)par_num, par_get_status_str(status)); } return status; } -//////////////////////////////////////////////////////////////////////////////// -/** -* Set signed 8-bit parameter -* -* @param[in] par_num - Parameter number (enumeration) -* @param[in] val - Value of parameter -* @return status - Status of operation -*/ -//////////////////////////////////////////////////////////////////////////////// -par_status_t par_set_i8(const par_num_t par_num, const int8_t val) +#if (1 == PAR_CFG_ENABLE_RESET_ALL_RAW) +/** + * @brief Reset all parameters to default values via raw storage restore. + * + * @note Unlike par_set_all_to_default(), this API restores grouped. + * storage directly from default mirrors instead of iterating over. + * all parameters through the normal setter path. + * + * @note This path is therefore typically faster for bulk reset, because. + * it avoids per-parameter runtime validation, on-change callback,. + * and setter-side range handling. + * + * @note Restore is performed as one grouped storage snapshot copy. + * Internal U8/U16/U32 width-group storage semantics are preserved. + * + * @pre Parameters must be initialized before usage. + * + * @return Status of operation. + */ +par_status_t par_reset_all_to_default_raw(void) { - par_status_t status = ePAR_OK; + PAR_ASSERT(true == par_is_init()); + if (true != par_is_init()) + return ePAR_ERROR_INIT; - // Check initialization - PAR_ASSERT( true == par_is_init()); - if ( true != par_is_init()) return ePAR_ERROR_INIT; - - // Check for invalid type - PAR_ASSERT( ePAR_TYPE_I8 == par_get_type(par_num)); - if( ePAR_TYPE_I8 != par_get_type(par_num)) return ePAR_ERROR_TYPE; - - pf_par_validation_t validation = g_par_cb_table[par_num].validation; - pf_par_on_change_cb_t on_change = g_par_cb_table[par_num].on_change; - // Get mutex - if ( ePAR_OK == par_acquire_mutex(par_num)) + if (ePAR_OK != par_acquire_mutex((par_num_t)0)) { - const par_type_t old_val = {.i8 = PAR_GET_I8_PRIV( par_num )}; + return ePAR_ERROR_MUTEX; + } - // Validated parameter value - if ((validation == NULL) || validation(par_num, (par_type_t){.i8 = val})) - { - status = par_set_i8_fast( par_num, val ); - } - else - { - status = ePAR_ERROR_VALUE; - } + memcpy(&gs_par_storage, &gs_par_default_mirror, sizeof(gs_par_storage)); - // Raise on change callback - const par_type_t new_val = {.i8 = PAR_GET_I8_PRIV( par_num )}; - if ((on_change != NULL) && (new_val.i8 != old_val.i8)) - { - on_change(par_num, new_val, old_val); - } + par_release_mutex((par_num_t)0); + + PAR_DBG_PRINT("PAR: raw reset all parameters to defaults"); + return ePAR_OK; +} +#endif +/** + * @brief Set all parameters to default value. + * + * @pre Parameters must be initialised before usage! + * @note When PAR_CFG_ENABLE_RESET_ALL_RAW = 1, this public API forwards. + * to par_reset_all_to_default_raw() for maximum reset speed. + * @note Otherwise it iterates through parameters and resets them via. + * par_set_to_default(), preserving fast default-restore semantics. + * + * @return Status of operation. + */ +par_status_t par_set_all_to_default(void) +{ +#if (1 == PAR_CFG_ENABLE_RESET_ALL_RAW) + return par_reset_all_to_default_raw(); +#else + par_status_t status = ePAR_OK; - par_release_mutex(par_num); + if (true != par_is_init()) + { + return ePAR_ERROR_INIT; } - else + + for (par_num_t par_num = 0; par_num < ePAR_NUM_OF; par_num++) { - status = ePAR_ERROR_MUTEX; + status |= par_set_to_default(par_num); } + PAR_DBG_PRINT("PAR: setting all parameters to defaults"); return status; +#endif } - -//////////////////////////////////////////////////////////////////////////////// /** -* Set unsigned 16-bit parameter -* -* @param[in] par_num - Parameter number (enumeration) -* @param[in] val - Value of parameter -* @return status - Status of operation -*/ -//////////////////////////////////////////////////////////////////////////////// -par_status_t par_set_u16(const par_num_t par_num, const uint16_t val) + * @brief Check if parameter changed from its default value. + * + * @param par_num Parameter number (enumeration). + * @param p_has_changed Pointer to changed indication. + * @return Status of operation. + */ +par_status_t par_has_changed(const par_num_t par_num, bool * const p_has_changed) { + const par_cfg_t *par_cfg = NULL; par_status_t status = ePAR_OK; - // Check initialization - PAR_ASSERT( true == par_is_init()); - if ( true != par_is_init()) return ePAR_ERROR_INIT; - - // Check for invalid type - PAR_ASSERT( ePAR_TYPE_U16 == par_get_type(par_num)); - if( ePAR_TYPE_U16 != par_get_type(par_num)) return ePAR_ERROR_TYPE; - - pf_par_validation_t validation = g_par_cb_table[par_num].validation; - pf_par_on_change_cb_t on_change = g_par_cb_table[par_num].on_change; - // Get mutex - if ( ePAR_OK == par_acquire_mutex(par_num)) + status = par_resolve_runtime(par_num, p_has_changed, true, &par_cfg); + if (ePAR_OK != status) { - const par_type_t old_val = {.u16 = PAR_GET_U16_PRIV( par_num )}; + return status; + } - // Validated parameter value - if ((validation == NULL) || validation(par_num, (par_type_t){.u16 = val})) - { - status = par_set_u16_fast( par_num, val ); - } - else - { - status = ePAR_ERROR_VALUE; - } - - // Raise on change callback - const par_type_t new_val = {.u16 = PAR_GET_U16_PRIV( par_num )}; - if ((on_change != NULL) && (new_val.u16 != old_val.u16)) - { - on_change(par_num, new_val, old_val); - } - - par_release_mutex(par_num); - } - else + switch (par_cfg->type) { - status = ePAR_ERROR_MUTEX; + case ePAR_TYPE_U8: + { + uint8_t cur = 0U; + const par_status_t status = par_get_u8(par_num, &cur); + if (ePAR_OK != status) + return status; + *p_has_changed = (cur != par_cfg->def.u8); + break; } - return status; -} - -//////////////////////////////////////////////////////////////////////////////// -/** -* Set signed 16-bit parameter -* -* @param[in] par_num - Parameter number (enumeration) -* @param[in] val - Value of parameter -* @return status - Status of operation -*/ -//////////////////////////////////////////////////////////////////////////////// -par_status_t par_set_i16(const par_num_t par_num, const int16_t val) -{ - par_status_t status = ePAR_OK; - - // Check initialization - PAR_ASSERT( true == par_is_init()); - if ( true != par_is_init()) return ePAR_ERROR_INIT; - - // Check for invalid type - PAR_ASSERT( ePAR_TYPE_I16 == par_get_type(par_num)); - if( ePAR_TYPE_I16 != par_get_type(par_num)) return ePAR_ERROR_TYPE; - - pf_par_validation_t validation = g_par_cb_table[par_num].validation; - pf_par_on_change_cb_t on_change = g_par_cb_table[par_num].on_change; - // Get mutex - if ( ePAR_OK == par_acquire_mutex(par_num)) + case ePAR_TYPE_I8: { - const par_type_t old_val = {.i16 = PAR_GET_I16_PRIV( par_num )}; - - // Validated parameter value - if ((validation == NULL) || validation(par_num, (par_type_t){.i16 = val})) - { - status = par_set_i16_fast( par_num, val ); - } - else - { - status = ePAR_ERROR_VALUE; - } - - // Raise on change callback - const par_type_t new_val = {.i16 = PAR_GET_I16_PRIV( par_num )}; - if ((on_change != NULL) && (new_val.i16 != old_val.i16)) - { - on_change(par_num, new_val, old_val); - } - - par_release_mutex(par_num); + int8_t cur = 0; + const par_status_t status = par_get_i8(par_num, &cur); + if (ePAR_OK != status) + return status; + *p_has_changed = (cur != par_cfg->def.i8); + break; } - else + + case ePAR_TYPE_U16: { - status = ePAR_ERROR_MUTEX; + uint16_t cur = 0U; + const par_status_t status = par_get_u16(par_num, &cur); + if (ePAR_OK != status) + return status; + *p_has_changed = (cur != par_cfg->def.u16); + break; } - return status; -} - -//////////////////////////////////////////////////////////////////////////////// -/** -* Set unsigned 32-bit parameter -* -* @param[in] par_num - Parameter number (enumeration) -* @param[in] val - Value of parameter -* @return status - Status of operation -*/ -//////////////////////////////////////////////////////////////////////////////// -par_status_t par_set_u32(const par_num_t par_num, const uint32_t val) -{ - par_status_t status = ePAR_OK; - - // Check initialization - PAR_ASSERT( true == par_is_init()); - if ( true != par_is_init()) return ePAR_ERROR_INIT; - - // Check for invalid type - PAR_ASSERT( ePAR_TYPE_U32 == par_get_type(par_num)); - if( ePAR_TYPE_U32 != par_get_type(par_num)) return ePAR_ERROR_TYPE; - - pf_par_validation_t validation = g_par_cb_table[par_num].validation; - pf_par_on_change_cb_t on_change = g_par_cb_table[par_num].on_change; - // Get mutex - if ( ePAR_OK == par_acquire_mutex(par_num)) + case ePAR_TYPE_I16: { - const par_type_t old_val = {.u32 = PAR_GET_U32_PRIV( par_num )}; - - // Validated parameter value - if ((validation == NULL) || validation(par_num, (par_type_t){.u32 = val})) - { - status = par_set_u32_fast( par_num, val ); - } - else - { - status = ePAR_ERROR_VALUE; - } - - // Raise on change callback - const par_type_t new_val = {.u32 = PAR_GET_U32_PRIV( par_num )}; - if ((on_change != NULL) && (new_val.u32 != old_val.u32)) - { - on_change(par_num, new_val, old_val); - } - - par_release_mutex(par_num); + int16_t cur = 0; + const par_status_t status = par_get_i16(par_num, &cur); + if (ePAR_OK != status) + return status; + *p_has_changed = (cur != par_cfg->def.i16); + break; } - else + + case ePAR_TYPE_U32: { - status = ePAR_ERROR_MUTEX; + uint32_t cur = 0U; + const par_status_t status = par_get_u32(par_num, &cur); + if (ePAR_OK != status) + return status; + *p_has_changed = (cur != par_cfg->def.u32); + break; } - return status; -} - -//////////////////////////////////////////////////////////////////////////////// -/** -* Set signed 32-bit parameter -* -* @param[in] par_num - Parameter number (enumeration) -* @param[in] val - Value of parameter -* @return status - Status of operation -*/ -//////////////////////////////////////////////////////////////////////////////// -par_status_t par_set_i32(const par_num_t par_num, const int32_t val) -{ - par_status_t status = ePAR_OK; - - // Check initialization - PAR_ASSERT( true == par_is_init()); - if ( true != par_is_init()) return ePAR_ERROR_INIT; - - // Check for invalid type - PAR_ASSERT( ePAR_TYPE_I32 == par_get_type(par_num)); - if( ePAR_TYPE_I32 != par_get_type(par_num)) return ePAR_ERROR_TYPE; - - pf_par_validation_t validation = g_par_cb_table[par_num].validation; - pf_par_on_change_cb_t on_change = g_par_cb_table[par_num].on_change; - // Get mutex - if ( ePAR_OK == par_acquire_mutex(par_num)) + case ePAR_TYPE_I32: { - const par_type_t old_val = {.i32 = PAR_GET_I32_PRIV( par_num )}; - - // Validated parameter value - if ((validation == NULL) || validation(par_num, (par_type_t){.i32 = val})) - { - status = par_set_i32_fast( par_num, val ); - } - else - { - status = ePAR_ERROR_VALUE; - } - - // Raise on change callback - const par_type_t new_val = {.i32 = PAR_GET_I32_PRIV( par_num )}; - if ((on_change != NULL) && (new_val.i32 != old_val.i32)) - { - on_change(par_num, new_val, old_val); - } - - par_release_mutex(par_num); + int32_t cur = 0; + const par_status_t status = par_get_i32(par_num, &cur); + if (ePAR_OK != status) + return status; + *p_has_changed = (cur != par_cfg->def.i32); + break; } - else + +#if (1 == PAR_CFG_ENABLE_TYPE_F32) + case ePAR_TYPE_F32: { - status = ePAR_ERROR_MUTEX; + float32_t cur = 0.0f; + const par_status_t status = par_get_f32(par_num, &cur); + if (ePAR_OK != status) + return status; + *p_has_changed = !par_f32_bits_equal(cur, par_cfg->def.f32); + break; + } +#endif + + case ePAR_TYPE_NUM_OF: + default: + PAR_ASSERT(0); + return ePAR_ERROR_TYPE; } - return status; + return ePAR_OK; } - -//////////////////////////////////////////////////////////////////////////////// /** -* Set floating value parameter -* -* @param[in] par_num - Parameter number (enumeration) -* @param[in] val - Value of parameter -* @return status - Status of operation -*/ -//////////////////////////////////////////////////////////////////////////////// -par_status_t par_set_f32(const par_num_t par_num, const float32_t val) + * @brief Get parameter value. + * + * @note Mandatory to cast input argument to appropriate type. E.g.: + * + * @code + * float32_t my_val = 0.0f;. + * par_get( ePAR_MY_VAR, (float32_t*) &my_val );. + * @endcode + * + * @note Input is the internal parameter number (`par_num_t`) from `par_def.h`, + * not the external parameter ID. + * + * @param par_num Parameter number (enumeration). + * @param p_val Parameter value. + * @return Status of operation. + */ +par_status_t par_get(const par_num_t par_num, void * const p_val) { + const par_cfg_t *par_cfg = NULL; par_status_t status = ePAR_OK; - // Check initialization - PAR_ASSERT( true == par_is_init()); - if ( true != par_is_init()) return ePAR_ERROR_INIT; - - // Check for invalid type - PAR_ASSERT( ePAR_TYPE_F32 == par_get_type(par_num)); - if( ePAR_TYPE_F32 != par_get_type(par_num)) return ePAR_ERROR_TYPE; + status = par_resolve_runtime(par_num, p_val, true, &par_cfg); + if (ePAR_OK != status) + { + return status; + } - pf_par_validation_t validation = g_par_cb_table[par_num].validation; - pf_par_on_change_cb_t on_change = g_par_cb_table[par_num].on_change; - // Get mutex - if ( ePAR_OK == par_acquire_mutex(par_num)) + switch (par_cfg->type) { - const par_type_t old_val = {.f32 = PAR_GET_F32_PRIV( par_num )}; + case ePAR_TYPE_U8: + return par_get_u8(par_num, (uint8_t *)p_val); - // Validated parameter value - if ((validation == NULL) || validation(par_num, (par_type_t){.f32 = val})) - { - status = par_set_f32_fast( par_num, val ); - } - else - { - status = ePAR_ERROR_VALUE; - } + case ePAR_TYPE_I8: + return par_get_i8(par_num, (int8_t *)p_val); - // Raise on change callback - const par_type_t new_val = {.f32 = PAR_GET_F32_PRIV( par_num )}; - if ((on_change != NULL) && (new_val.f32 != old_val.f32)) - { - on_change(par_num, new_val, old_val); - } + case ePAR_TYPE_U16: + return par_get_u16(par_num, (uint16_t *)p_val); - par_release_mutex(par_num); - } - else - { - status = ePAR_ERROR_MUTEX; - } + case ePAR_TYPE_I16: + return par_get_i16(par_num, (int16_t *)p_val); - return status; -} + case ePAR_TYPE_U32: + return par_get_u32(par_num, (uint32_t *)p_val); -//////////////////////////////////////////////////////////////////////////////// -/** -* Set unsigned 8-bit parameter fast -* -* @note Using *(volatile uint8_t*) prevents store tearing as explained -* here: https://lwn.net/Articles/793253/ -* -* @param[in] par_num - Parameter number (enumeration) -* @param[in] val - Value of parameter -* @return status - Status of operation -*/ -//////////////////////////////////////////////////////////////////////////////// -par_status_t par_set_u8_fast(const par_num_t par_num, const uint8_t val) -{ - PAR_ASSERT( true == par_is_init()); - PAR_ASSERT( ePAR_TYPE_U8 == par_get_type(par_num)); + case ePAR_TYPE_I32: + return par_get_i32(par_num, (int32_t *)p_val); - const par_range_t range = par_get_range(par_num); +#if (1 == PAR_CFG_ENABLE_TYPE_F32) + case ePAR_TYPE_F32: + return par_get_f32(par_num, (float32_t *)p_val); +#endif - if ( val > range.max.u8 ) - { - PAR_SET_U8_PRIV( par_num, range.max.u8 ); - return ePAR_WAR_LIMITED; - } - else if ( val < range.min.u8 ) - { - PAR_SET_U8_PRIV( par_num, range.min.u8 ); - return ePAR_WAR_LIMITED; - } - else - { - PAR_SET_U8_PRIV( par_num, val ); - return ePAR_OK; + case ePAR_TYPE_NUM_OF: + default: + PAR_ASSERT(0); + return ePAR_ERROR_TYPE; } } - -//////////////////////////////////////////////////////////////////////////////// /** -* Set signed 8-bit parameter fast -* -* @param[in] par_num - Parameter number (enumeration) -* @param[in] val - Value of parameter -* @return status - Status of operation -*/ -//////////////////////////////////////////////////////////////////////////////// -par_status_t par_set_i8_fast(const par_num_t par_num, const int8_t val) + * @brief Get parameter value by ID. + * + * @param id Parameter ID number. + * @param p_val Pointer to value. + * @return Status of operation. + */ +#if (1 == PAR_CFG_ENABLE_ID) +par_status_t par_get_by_id(const uint16_t id, void * const p_val) { - PAR_ASSERT( true == par_is_init()); - PAR_ASSERT( ePAR_TYPE_I8 == par_get_type(par_num)); - - const par_range_t range = par_get_range(par_num); + par_num_t par_num = 0U; + const par_status_t status = par_get_num_by_id(id, &par_num); - if ( val > range.max.i8 ) + if (ePAR_OK != status) { - PAR_SET_I8_PRIV( par_num, range.max.i8 ); - return ePAR_WAR_LIMITED; - } - else if ( val < range.min.i8 ) - { - PAR_SET_I8_PRIV( par_num, range.min.i8 ); - return ePAR_WAR_LIMITED; - } - else - { - PAR_SET_I8_PRIV( par_num, val ); - return ePAR_OK; + return status; } -} -//////////////////////////////////////////////////////////////////////////////// + return par_get(par_num, p_val); +} +#endif /** -* Set unsigned 16-bit parameter fast -* -* @param[in] par_num - Parameter number (enumeration) -* @param[in] val - Value of parameter -* @return status - Status of operation -*/ -//////////////////////////////////////////////////////////////////////////////// -par_status_t par_set_u16_fast(const par_num_t par_num, const uint16_t val) + * @brief Get parameter default value. + * *. + * @param par_num Parameter number (enumeration). + * @param p_val Parameter default value. + * @return Status of operation. + */ +par_status_t par_get_default(const par_num_t par_num, void * const p_val) { - PAR_ASSERT( true == par_is_init()); - PAR_ASSERT( ePAR_TYPE_U16 == par_get_type(par_num)); - - const par_range_t range = par_get_range(par_num); + const par_cfg_t *par_cfg = NULL; + par_status_t status = ePAR_OK; - if ( val > range.max.u16 ) - { - PAR_SET_U16_PRIV( par_num, range.max.u16 ); - return ePAR_WAR_LIMITED; - } - else if ( val < range.min.u16 ) + status = par_resolve_metadata(par_num, p_val, true, &par_cfg); + if (ePAR_OK != status) { - PAR_SET_U16_PRIV( par_num, range.min.u16 ); - return ePAR_WAR_LIMITED; + return status; } - else + + switch (par_cfg->type) { - PAR_SET_U16_PRIV( par_num, val ); - return ePAR_OK; - } -} + case ePAR_TYPE_U8: + *(uint8_t *)p_val = (uint8_t)par_cfg->def.u8; + break; -//////////////////////////////////////////////////////////////////////////////// -/** -* Set signed 16-bit parameter fast -* -* @param[in] par_num - Parameter number (enumeration) -* @param[in] val - Value of parameter -* @return status - Status of operation -*/ -//////////////////////////////////////////////////////////////////////////////// -par_status_t par_set_i16_fast(const par_num_t par_num, const int16_t val) -{ - PAR_ASSERT( true == par_is_init()); - PAR_ASSERT( ePAR_TYPE_I16 == par_get_type(par_num)); + case ePAR_TYPE_I8: + *(int8_t *)p_val = (int8_t)par_cfg->def.i8; + break; - const par_range_t range = par_get_range(par_num); + case ePAR_TYPE_U16: + *(uint16_t *)p_val = (uint16_t)par_cfg->def.u16; + break; - if ( val > range.max.i16 ) - { - PAR_SET_I16_PRIV( par_num, range.max.i16 ); - return ePAR_WAR_LIMITED; - } - else if ( val < range.min.i16 ) - { - PAR_SET_I16_PRIV( par_num, range.min.i16 ); - return ePAR_WAR_LIMITED; - } - else - { - PAR_SET_I16_PRIV( par_num, val ); - return ePAR_OK; - } -} + case ePAR_TYPE_I16: + *(int16_t *)p_val = (int16_t)par_cfg->def.i16; + break; -//////////////////////////////////////////////////////////////////////////////// -/** -* Set unsigned 16-bit parameter fast -* -* @param[in] par_num - Parameter number (enumeration) -* @param[in] val - Value of parameter -* @return status - Status of operation -*/ -//////////////////////////////////////////////////////////////////////////////// -par_status_t par_set_u32_fast(const par_num_t par_num, const uint32_t val) -{ - PAR_ASSERT( true == par_is_init()); - PAR_ASSERT( ePAR_TYPE_U32 == par_get_type(par_num)); + case ePAR_TYPE_U32: + *(uint32_t *)p_val = (uint32_t)par_cfg->def.u32; + break; - const par_range_t range = par_get_range(par_num); + case ePAR_TYPE_I32: + *(int32_t *)p_val = (int32_t)par_cfg->def.i32; + break; - if ( val > range.max.u32 ) - { - PAR_SET_U32_PRIV( par_num, range.max.u32 ); - return ePAR_WAR_LIMITED; - } - else if ( val < range.min.u32 ) - { - PAR_SET_U32_PRIV( par_num, range.min.u32 ); - return ePAR_WAR_LIMITED; - } - else - { - PAR_SET_U32_PRIV( par_num, val ); - return ePAR_OK; +#if (1 == PAR_CFG_ENABLE_TYPE_F32) + case ePAR_TYPE_F32: + *(float32_t *)p_val = (float32_t)par_cfg->def.f32; + break; +#endif + + case ePAR_TYPE_NUM_OF: + default: + PAR_ASSERT(0); + return ePAR_ERROR_TYPE; } -} -//////////////////////////////////////////////////////////////////////////////// + return ePAR_OK; +} /** -* Set signed 32-bit parameter fast -* -* @param[in] par_num - Parameter number (enumeration) -* @param[in] val - Value of parameter -* @return status - Status of operation -*/ -//////////////////////////////////////////////////////////////////////////////// -par_status_t par_set_i32_fast(const par_num_t par_num, const int32_t val) + * @brief Get parameter configurations. + * + * @note In case parameter is not found it return NULL! + * + * @param par_num Parameter number (enumeration). + * @return Parameter configuration. + */ +const par_cfg_t *par_get_config(const par_num_t par_num) { - PAR_ASSERT( true == par_is_init()); - PAR_ASSERT( ePAR_TYPE_I32 == par_get_type(par_num)); - - const par_range_t range = par_get_range(par_num); + PAR_ASSERT(par_num < ePAR_NUM_OF); + if (par_num >= ePAR_NUM_OF) + return NULL; - if ( val > range.max.i32 ) - { - PAR_SET_I32_PRIV( par_num, range.max.i32 ); - return ePAR_WAR_LIMITED; - } - else if ( val < range.min.i32 ) - { - PAR_SET_I32_PRIV( par_num, range.min.i32 ); - return ePAR_WAR_LIMITED; - } - else - { - PAR_SET_I32_PRIV( par_num, val ); - return ePAR_OK; - } + return par_cfg_get(par_num); } - -//////////////////////////////////////////////////////////////////////////////// /** -* Set floating value parameter fast -* -* @param[in] par_num - Parameter number (enumeration) -* @param[in] val - Value of parameter -* @return status - Status of operation -*/ -//////////////////////////////////////////////////////////////////////////////// -par_status_t par_set_f32_fast(const par_num_t par_num, const float32_t val) + * @brief Get parameter name. + * + * @param par_num Parameter number (enumeration). + * @return Parameter name. + */ +#if (1 == PAR_CFG_ENABLE_NAME) +const char *par_get_name(const par_num_t par_num) { - PAR_ASSERT( true == par_is_init()); - PAR_ASSERT( ePAR_TYPE_F32 == par_get_type(par_num)); + const par_cfg_t *par_cfg = NULL; - const par_range_t range = par_get_range(par_num); - - if ( val > range.max.f32 ) - { - PAR_SET_F32_PRIV( par_num, range.max.f32 ); - return ePAR_WAR_LIMITED; - } - else if ( val < range.min.f32 ) - { - PAR_SET_F32_PRIV( par_num, range.min.f32 ); - return ePAR_WAR_LIMITED; - } - else + if (ePAR_OK == par_resolve_metadata(par_num, NULL, false, &par_cfg)) { - PAR_SET_F32_PRIV( par_num, val ); - return ePAR_OK; + return par_cfg->name; } -} -//////////////////////////////////////////////////////////////////////////////// + return NULL; +} +#endif /** -* Set unsigned 8-bit parameter ANDing with current set value fast -* -* @param[in] par_num - Parameter number (enumeration) -* @param[in] val - Value of parameter -* @return status - Status of operation -*/ -//////////////////////////////////////////////////////////////////////////////// -par_status_t par_bitand_set_u8_fast(const par_num_t par_num, const uint8_t val) + * @brief Get parameter value range. + * + * @param par_num Parameter number (enumeration). + * @return Parameter min/max range. + */ +#if (1 == PAR_CFG_ENABLE_RANGE) +par_range_t par_get_range(const par_num_t par_num) { - PAR_ASSERT( true == par_is_init()); - PAR_ASSERT( ePAR_TYPE_U8 == par_get_type(par_num)); + par_range_t range = { 0 }; + const par_cfg_t *par_cfg = NULL; - const par_range_t range = par_get_range(par_num); - - if ( val > range.max.u8 ) - { - atomic_fetch_and_explicit( &gpu8_par_value[gu32_par_offset[par_num]], range.max.u8, memory_order_relaxed ); - return ePAR_WAR_LIMITED; - } - else if ( val < range.min.u8 ) - { - atomic_fetch_and_explicit( &gpu8_par_value[gu32_par_offset[par_num]], range.min.u8, memory_order_relaxed ); - return ePAR_WAR_LIMITED; - } - else + if (ePAR_OK == par_resolve_metadata(par_num, NULL, false, &par_cfg)) { - atomic_fetch_and_explicit( &gpu8_par_value[gu32_par_offset[par_num]], val, memory_order_relaxed ); - return ePAR_OK; + return par_cfg->range; } -} -//////////////////////////////////////////////////////////////////////////////// + return range; +} +#endif /** -* Set unsigned 16-bit parameter ANDing with current set value fast -* -* @param[in] par_num - Parameter number (enumeration) -* @param[in] val - Value of parameter -* @return status - Status of operation -*/ -//////////////////////////////////////////////////////////////////////////////// -par_status_t par_bitand_set_u16_fast(const par_num_t par_num, const uint16_t val) + * @brief Get parameter unit. + * + * @param par_num Parameter number (enumeration). + * @return Parameter unit. + */ +#if (1 == PAR_CFG_ENABLE_UNIT) +const char *par_get_unit(const par_num_t par_num) { - PAR_ASSERT( true == par_is_init()); - PAR_ASSERT( ePAR_TYPE_U16 == par_get_type(par_num)); + const par_cfg_t *par_cfg = NULL; - const par_range_t range = par_get_range(par_num); - - if ( val > range.max.u16 ) - { - atomic_fetch_and_explicit( &gpu16_par_value[gu32_par_offset[par_num]], range.max.u16, memory_order_relaxed ); - return ePAR_WAR_LIMITED; - } - else if ( val < range.min.u16 ) - { - atomic_fetch_and_explicit( &gpu16_par_value[gu32_par_offset[par_num]], range.min.u16, memory_order_relaxed ); - return ePAR_WAR_LIMITED; - } - else + if (ePAR_OK == par_resolve_metadata(par_num, NULL, false, &par_cfg)) { - atomic_fetch_and_explicit( &gpu16_par_value[gu32_par_offset[par_num]], val, memory_order_relaxed ); - return ePAR_OK; + return par_cfg->unit; } -} -//////////////////////////////////////////////////////////////////////////////// + return NULL; +} +#endif /** -* Set unsigned 16-bit parameter ANDing with current set value fast -* -* @param[in] par_num - Parameter number (enumeration) -* @param[in] val - Value of parameter -* @return status - Status of operation -*/ -//////////////////////////////////////////////////////////////////////////////// -par_status_t par_bitand_set_u32_fast(const par_num_t par_num, const uint32_t val) + * @brief Get parameter description. + * + * @param par_num Parameter number (enumeration). + * @return Parameter description. + */ +#if (1 == PAR_CFG_ENABLE_DESC) +const char *par_get_desc(const par_num_t par_num) { - PAR_ASSERT( true == par_is_init()); - PAR_ASSERT( ePAR_TYPE_U32 == par_get_type(par_num)); - - const par_range_t range = par_get_range(par_num); + const par_cfg_t *par_cfg = NULL; - if ( val > range.max.u32 ) + if (ePAR_OK == par_resolve_metadata(par_num, NULL, false, &par_cfg)) { - atomic_fetch_and_explicit( &gpu32_par_value[gu32_par_offset[par_num]], range.max.u32, memory_order_relaxed ); - return ePAR_WAR_LIMITED; - } - else if ( val < range.min.u32 ) - { - atomic_fetch_and_explicit( &gpu32_par_value[gu32_par_offset[par_num]], range.min.u32, memory_order_relaxed ); - return ePAR_WAR_LIMITED; - } - else - { - atomic_fetch_and_explicit( &gpu32_par_value[gu32_par_offset[par_num]], val, memory_order_relaxed ); - return ePAR_OK; + return par_cfg->desc; } -} -//////////////////////////////////////////////////////////////////////////////// + return NULL; +} +#endif /** -* Set unsigned 8-bit parameter ORing with current set value fast -* -* @param[in] par_num - Parameter number (enumeration) -* @param[in] val - Value of parameter -* @return status - Status of operation -*/ -//////////////////////////////////////////////////////////////////////////////// -par_status_t par_bitor_set_u8_fast(const par_num_t par_num, const uint8_t val) + * @brief Get parameter type. + * + * @param par_num Parameter number (enumeration). + * @return Parameter data type. + */ +par_type_list_t par_get_type(const par_num_t par_num) { - PAR_ASSERT( true == par_is_init()); - PAR_ASSERT( ePAR_TYPE_U8 == par_get_type(par_num)); - - const par_range_t range = par_get_range(par_num); + const par_cfg_t *par_cfg = NULL; - if ( val > range.max.u8 ) - { - atomic_fetch_or_explicit( &gpu8_par_value[gu32_par_offset[par_num]], range.max.u8, memory_order_relaxed ); - return ePAR_WAR_LIMITED; - } - else if ( val < range.min.u8 ) + if (ePAR_OK == par_resolve_metadata(par_num, NULL, false, &par_cfg)) { - atomic_fetch_or_explicit( &gpu8_par_value[gu32_par_offset[par_num]], range.min.u8, memory_order_relaxed ); - return ePAR_WAR_LIMITED; - } - else - { - atomic_fetch_or_explicit( &gpu8_par_value[gu32_par_offset[par_num]], val, memory_order_relaxed ); - return ePAR_OK; + return par_cfg->type; } -} -//////////////////////////////////////////////////////////////////////////////// + return ePAR_TYPE_NUM_OF; +} /** -* Set unsigned 16-bit parameter ORing with current set value fast -* -* @param[in] par_num - Parameter number (enumeration) -* @param[in] val - Value of parameter -* @return status - Status of operation -*/ -//////////////////////////////////////////////////////////////////////////////// -par_status_t par_bitor_set_u16_fast(const par_num_t par_num, const uint16_t val) + * @brief Get parameter external capability mask. + * + * @param par_num Parameter number (enumeration). + * @return Parameter access. + */ +#if (1 == PAR_CFG_ENABLE_ACCESS) +par_access_t par_get_access(const par_num_t par_num) { - PAR_ASSERT( true == par_is_init()); - PAR_ASSERT( ePAR_TYPE_U16 == par_get_type(par_num)); + const par_cfg_t *par_cfg = NULL; - const par_range_t range = par_get_range(par_num); - - if ( val > range.max.u16 ) - { - atomic_fetch_or_explicit( &gpu16_par_value[gu32_par_offset[par_num]], range.max.u16, memory_order_relaxed ); - return ePAR_WAR_LIMITED; - } - else if ( val < range.min.u16 ) - { - atomic_fetch_or_explicit( &gpu16_par_value[gu32_par_offset[par_num]], range.min.u16, memory_order_relaxed ); - return ePAR_WAR_LIMITED; - } - else + if (ePAR_OK == par_resolve_metadata(par_num, NULL, false, &par_cfg)) { - atomic_fetch_or_explicit( &gpu16_par_value[gu32_par_offset[par_num]], val, memory_order_relaxed ); - return ePAR_OK; + return par_cfg->access; } -} -//////////////////////////////////////////////////////////////////////////////// -/** -* Set unsigned 16-bit parameter ORing with current set value fast -* -* @param[in] par_num - Parameter number (enumeration) -* @param[in] val - Value of parameter -* @return status - Status of operation -*/ -//////////////////////////////////////////////////////////////////////////////// -par_status_t par_bitor_set_u32_fast(const par_num_t par_num, const uint32_t val) + return ePAR_ACCESS_NONE; +} +#endif +#if (1 == PAR_CFG_ENABLE_ROLE_POLICY) +par_role_t par_get_read_roles(const par_num_t par_num) { - PAR_ASSERT( true == par_is_init()); - PAR_ASSERT( ePAR_TYPE_U32 == par_get_type(par_num)); + const par_cfg_t *par_cfg = NULL; - const par_range_t range = par_get_range(par_num); - - if ( val > range.max.u32 ) + if (ePAR_OK == par_resolve_metadata(par_num, NULL, false, &par_cfg)) { - atomic_fetch_or_explicit( &gpu32_par_value[gu32_par_offset[par_num]], range.max.u32, memory_order_relaxed ); - return ePAR_WAR_LIMITED; + return par_cfg->read_roles; } - else if ( val < range.min.u32 ) - { - atomic_fetch_or_explicit( &gpu32_par_value[gu32_par_offset[par_num]], range.min.u32, memory_order_relaxed ); - return ePAR_WAR_LIMITED; - } - else - { - atomic_fetch_or_explicit( &gpu32_par_value[gu32_par_offset[par_num]], val, memory_order_relaxed ); - return ePAR_OK; - } -} -//////////////////////////////////////////////////////////////////////////////// -/** -* Set parameter to default value -* -* @pre Parameters must be initialised before usage! -* -* @param[in] par_num - Parameter number (enumeration) -* @return status - Status of operation -*/ -//////////////////////////////////////////////////////////////////////////////// -par_status_t par_set_to_default(const par_num_t par_num) -{ - return par_set(par_num, &(par_get_config(par_num)->def)); + return ePAR_ROLE_NONE; } -//////////////////////////////////////////////////////////////////////////////// -/** -* Set all parameters to default value -* -* @pre Parameters must be initialised before usage! -* -* @return status - Status of operation -*/ -//////////////////////////////////////////////////////////////////////////////// -par_status_t par_set_all_to_default(void) +par_role_t par_get_write_roles(const par_num_t par_num) { - for ( par_num_t par_num = 0; par_num < ePAR_NUM_OF; par_num++ ) + const par_cfg_t *par_cfg = NULL; + + if (ePAR_OK == par_resolve_metadata(par_num, NULL, false, &par_cfg)) { - // Ignore return as it is not possible to return other that OK - (void) par_set_to_default( par_num ); + return par_cfg->write_roles; } - PAR_DBG_PRINT( "PAR: Setting all parameters to default" ); - return ePAR_OK; + return ePAR_ROLE_NONE; } -//////////////////////////////////////////////////////////////////////////////// -/** -* Check if parameter changed from its default value -* -* @param[in] par_num - Parameter number (enumeration) -* @param[out] p_has_changed - Pointer to changed indication -* @return status - Status of operation -*/ -//////////////////////////////////////////////////////////////////////////////// -par_status_t par_has_changed(const par_num_t par_num, bool *const p_has_changed) +bool par_can_read(const par_num_t par_num, const par_role_t roles) { - const par_cfg_t * const par_cfg = par_get_config(par_num); + const par_cfg_t *par_cfg = NULL; - switch ( par_cfg->type ) + if ((false == par_roles_are_valid(roles)) || + (ePAR_OK != par_resolve_metadata(par_num, NULL, false, &par_cfg)) || + (NULL == par_cfg)) { - case ePAR_TYPE_U8: - *p_has_changed = (par_get_u8(par_num) != par_cfg->def.u8); - break; - - case ePAR_TYPE_I8: - *p_has_changed = (par_get_i8(par_num) != par_cfg->def.i8); - break; - - case ePAR_TYPE_U16: - *p_has_changed = (par_get_u16(par_num) != par_cfg->def.u16); - break; - - case ePAR_TYPE_I16: - *p_has_changed = (par_get_i16(par_num) != par_cfg->def.i16); - break; - - case ePAR_TYPE_U32: - *p_has_changed = (par_get_u32(par_num) != par_cfg->def.u32); - break; - - case ePAR_TYPE_I32: - *p_has_changed = (par_get_i32(par_num) != par_cfg->def.i32); - break; - - case ePAR_TYPE_F32: - *p_has_changed = (par_get_f32(par_num) != par_cfg->def.f32); - break; - - case ePAR_TYPE_NUM_OF: - default: - PAR_ASSERT( 0 ); - return ePAR_ERROR_TYPE; + return false; } - return ePAR_OK; -} - -//////////////////////////////////////////////////////////////////////////////// -/** -* Get parameter value -* -* @note Mandatory to cast input argument to appropriate type. E.g.: -* -* @code -* float32_t my_val = 0.0f; -* par_get( ePAR_MY_VAR, (float32_t*) &my_val ); -* @endcode -* -* @note Input is parameter number (enumeration) defined in par_cfg.h and not -* parameter ID number! -* -* @param[in] par_num - Parameter number (enumeration) -* @param[out] p_val - Parameter value -* @return status - Status of operation -*/ -//////////////////////////////////////////////////////////////////////////////// -par_status_t par_get(const par_num_t par_num, void * const p_val) -{ - switch ( par_get_type(par_num)) +#if (1 == PAR_CFG_ENABLE_ACCESS) + if (false == par_access_has_read(par_cfg->access)) { - case ePAR_TYPE_U8: - *(uint8_t*) p_val = par_get_u8(par_num); - break; - - case ePAR_TYPE_I8: - *(int8_t*) p_val = par_get_i8(par_num); - break; - - case ePAR_TYPE_U16: - *(uint16_t*) p_val = par_get_u16(par_num); - break; - - case ePAR_TYPE_I16: - *(int16_t*) p_val = par_get_i16(par_num); - break; - - case ePAR_TYPE_U32: - *(uint32_t*) p_val = par_get_u32(par_num); - break; - - case ePAR_TYPE_I32: - *(int32_t*) p_val = par_get_i32(par_num); - break; - - case ePAR_TYPE_F32: - *(float32_t*) p_val = par_get_f32(par_num); - break; - - case ePAR_TYPE_NUM_OF: - default: - PAR_ASSERT( 0 ); - return ePAR_ERROR_TYPE; + return false; } +#endif - return ePAR_OK; + return (0U != ((uint32_t)par_cfg->read_roles & (uint32_t)roles)); } -//////////////////////////////////////////////////////////////////////////////// -/** -* Get parameter value by ID -* -* @param[in] id - Parameter ID number -* @param[out] p_val - Pointer to value -* @return status - Status of operation -*/ -//////////////////////////////////////////////////////////////////////////////// -par_status_t par_get_by_id(const uint16_t id, void * const p_val) +bool par_can_write(const par_num_t par_num, const par_role_t roles) { - par_num_t par_num; + const par_cfg_t *par_cfg = NULL; - if ( ePAR_OK == par_get_num_by_id( id, &par_num )) + if ((false == par_roles_are_valid(roles)) || + (ePAR_OK != par_resolve_metadata(par_num, NULL, false, &par_cfg)) || + (NULL == par_cfg)) { - return par_get( par_num, p_val ); + return false; } - else + +#if (1 == PAR_CFG_ENABLE_ACCESS) + if (false == par_access_has_write(par_cfg->access)) { - return ePAR_ERROR; + return false; } -} - -//////////////////////////////////////////////////////////////////////////////// -/** -* Get unsigned 8-bit parameter value -* -* @note Returning as *(volatile uint8_t*) prevent load tearing as explained -* here: https://lwn.net/Articles/793253/ -* -* @param[in] par_num - Parameter number (enumeration) -* @return value - Value of parameter -*/ -//////////////////////////////////////////////////////////////////////////////// -uint8_t par_get_u8(const par_num_t par_num) -{ - // Check initialization - PAR_ASSERT( true == par_is_init()); - if ( true != par_is_init()) return ePAR_ERROR_INIT; - - // Check for invalid type - PAR_ASSERT( ePAR_TYPE_U8 == par_get_type(par_num)); - if( ePAR_TYPE_U8 != par_get_type(par_num)) return ePAR_ERROR_TYPE; - - return PAR_GET_U8_PRIV( par_num ); -} - -//////////////////////////////////////////////////////////////////////////////// -/** -* Get signed 8-bit parameter value -* -* @param[in] par_num - Parameter number (enumeration) -* @return value - Value of parameter -*/ -//////////////////////////////////////////////////////////////////////////////// -int8_t par_get_i8(const par_num_t par_num) -{ - // Check initialization - PAR_ASSERT( true == par_is_init()); - if ( true != par_is_init()) return ePAR_ERROR_INIT; - - // Check for invalid type - PAR_ASSERT( ePAR_TYPE_I8 == par_get_type(par_num)); - if( ePAR_TYPE_I8 != par_get_type(par_num)) return ePAR_ERROR_TYPE; +#endif - return PAR_GET_I8_PRIV( par_num ); + return (0U != ((uint32_t)par_cfg->write_roles & (uint32_t)roles)); } -//////////////////////////////////////////////////////////////////////////////// +#endif /** -* Get unsigned 16-bit parameter value -* -* @param[in] par_num - Parameter number (enumeration) -* @return value - Value of parameter -*/ -//////////////////////////////////////////////////////////////////////////////// -uint16_t par_get_u16(const par_num_t par_num) + * @brief Is parameter persistent (does it store to NVM). + * + * @param par_num Parameter number (enumeration). + * @return True if parameter is persistent. + */ +#if (1 == PAR_CFG_NVM_EN) +bool par_is_persistent(const par_num_t par_num) { - // Check initialization - PAR_ASSERT( true == par_is_init()); - if ( true != par_is_init()) return ePAR_ERROR_INIT; + const par_cfg_t *par_cfg = NULL; - // Check for invalid type - PAR_ASSERT( ePAR_TYPE_U16 == par_get_type(par_num)); - if( ePAR_TYPE_U16 != par_get_type(par_num)) return ePAR_ERROR_TYPE; + if (ePAR_OK == par_resolve_metadata(par_num, NULL, false, &par_cfg)) + { + return par_cfg->persistent; + } - return PAR_GET_U16_PRIV( par_num ); + return false; } - -//////////////////////////////////////////////////////////////////////////////// +#endif /** -* Get signed 16-bit parameter value -* -* @param[in] par_num - Parameter number (enumeration) -* @return value - Value of parameter -*/ -//////////////////////////////////////////////////////////////////////////////// -int16_t par_get_i16(const par_num_t par_num) + * @brief Get parameter number (enumeration) by ID. + * + * @note This API reads the compile-time static ID map only. + * It does not require par_init(), because it does not access. + * runtime parameter storage. + * + * @param id Parameter ID. + * @param p_par_num Pointer to parameter enumeration number. + * @return Status of operation. + */ +#if (1 == PAR_CFG_ENABLE_ID) +par_status_t par_get_num_by_id(const uint16_t id, par_num_t * const p_par_num) { - // Check initialization - PAR_ASSERT( true == par_is_init()); - if ( true != par_is_init()) return ePAR_ERROR_INIT; - - // Check for invalid type - PAR_ASSERT( ePAR_TYPE_I16 == par_get_type(par_num)); - if( ePAR_TYPE_I16 != par_get_type(par_num)) return ePAR_ERROR_TYPE; + if (NULL == p_par_num) + { + return ePAR_ERROR_PARAM; + } - return PAR_GET_I16_PRIV( par_num ); -} + const uint32_t bucket_idx = par_hash_id(id); + const par_id_map_entry_t * const bucket = &g_par_id_map_static[bucket_idx]; -//////////////////////////////////////////////////////////////////////////////// -/** -* Get unsigned 32-bit parameter value -* -* @param[in] par_num - Parameter number (enumeration) -* @return value - Value of parameter -*/ -//////////////////////////////////////////////////////////////////////////////// -uint32_t par_get_u32(const par_num_t par_num) -{ - // Check initialization - PAR_ASSERT( true == par_is_init()); - if ( true != par_is_init()) return ePAR_ERROR_INIT; + if ((0u != bucket->used) && (id == bucket->id)) + { + if (bucket->par_num >= ePAR_NUM_OF) + { + return ePAR_ERROR_PAR_NUM; + } - // Check for invalid type - PAR_ASSERT( ePAR_TYPE_U32 == par_get_type(par_num)); - if( ePAR_TYPE_U32 != par_get_type(par_num)) return ePAR_ERROR_TYPE; + *p_par_num = bucket->par_num; + return ePAR_OK; + } - return PAR_GET_U32_PRIV( par_num ); + return ePAR_ERROR; } - -//////////////////////////////////////////////////////////////////////////////// /** -* Get signed 32-bit parameter value -* -* @param[in] par_num - Parameter number (enumeration) -* @return value - Value of parameter -*/ -//////////////////////////////////////////////////////////////////////////////// -int32_t par_get_i32(const par_num_t par_num) + * @brief Get parameter ID by number (enumeration). + * + * @param par_num Parameter number. + * @param p_id Pointer to parameter ID. + * @return Status of operation. + */ +par_status_t par_get_id_by_num(const par_num_t par_num, uint16_t * const p_id) { - // Check initialization - PAR_ASSERT( true == par_is_init()); - if ( true != par_is_init()) return ePAR_ERROR_INIT; - - // Check for invalid type - PAR_ASSERT( ePAR_TYPE_I32 == par_get_type(par_num)); - if( ePAR_TYPE_I32 != par_get_type(par_num)) return ePAR_ERROR_TYPE; + if (NULL == p_id) + { + return ePAR_ERROR_PARAM; + } - return PAR_GET_I32_PRIV( par_num ); -} + if (par_num >= ePAR_NUM_OF) + { + return ePAR_ERROR_PAR_NUM; + } -//////////////////////////////////////////////////////////////////////////////// -/** -* Get floating value parameter value -* -* @param[in] par_num - Parameter number (enumeration) -* @return value - Value of parameter -*/ -//////////////////////////////////////////////////////////////////////////////// -float32_t par_get_f32(const par_num_t par_num) -{ - // Check initialization - PAR_ASSERT( true == par_is_init()); - if ( true != par_is_init()) return ePAR_ERROR_INIT; + const par_cfg_t * const par_cfg = par_get_config(par_num); - // Check for invalid type - PAR_ASSERT( ePAR_TYPE_F32 == par_get_type(par_num)); - if( ePAR_TYPE_F32 != par_get_type(par_num)) return ePAR_ERROR_TYPE; + if (NULL != par_cfg) + { + *p_id = par_cfg->id; + return ePAR_OK; + } - return PAR_GET_F32_PRIV( par_num ); + return ePAR_ERROR; } +#endif -//////////////////////////////////////////////////////////////////////////////// +#if (1 == PAR_CFG_NVM_EN) /** -* Get parameter default value -** -* @param[in] par_num - Parameter number (enumeration) -* @param[out] p_val - Parameter default value -* @return status - Status of operation -*/ -//////////////////////////////////////////////////////////////////////////////// -par_status_t par_get_default(const par_num_t par_num, void * const p_val) + * @brief Is parameter value changed. + * + * @param par_num Parameter number. + * @param p_val Parameter value. + * @return True if parameter value is different from current. + */ +static par_status_t par_is_value_changed(const par_num_t par_num, const void *p_val, bool * const p_value_changed) { - // Check initialization - PAR_ASSERT( true == par_is_init()); - if ( true != par_is_init()) return ePAR_ERROR_INIT; + const par_cfg_t *par_cfg = NULL; + par_status_t status = ePAR_OK; - switch ( par_get_type( par_num )) + if ((NULL == p_val) || (NULL == p_value_changed)) { - case ePAR_TYPE_U8: - *(uint8_t*) p_val = (uint8_t) par_get_config(par_num)->def.u8; - break; - - case ePAR_TYPE_I8: - *(int8_t*) p_val = (int8_t) par_get_config(par_num)->def.i8; - break; - - case ePAR_TYPE_U16: - *(uint16_t*) p_val = (uint16_t) par_get_config(par_num)->def.u16; - break; - - case ePAR_TYPE_I16: - *(int16_t*) p_val = (int16_t) par_get_config(par_num)->def.i16; - break; - - case ePAR_TYPE_U32: - *(uint32_t*) p_val = (uint32_t) par_get_config(par_num)->def.u32; - break; - - case ePAR_TYPE_I32: - *(int32_t*) p_val = (int32_t) par_get_config(par_num)->def.i32; - break; - - case ePAR_TYPE_F32: - *(float32_t*) p_val = (float32_t) par_get_config(par_num)->def.f32; - break; - - case ePAR_TYPE_NUM_OF: - default: - PAR_ASSERT( 0 ); - return ePAR_ERROR_TYPE; + return ePAR_ERROR_PARAM; } - return ePAR_OK; -} - -//////////////////////////////////////////////////////////////////////////////// -/** -* Check if parameter changed from its default value -* -* @param[in] par_num - Parameter number (enumeration) -* @return true if parameter value has been changed -*/ -//////////////////////////////////////////////////////////////////////////////// -bool par_is_changed(const par_num_t par_num) -{ - switch ( par_get_type(par_num)) + status = par_resolve_runtime(par_num, NULL, false, &par_cfg); + if (ePAR_OK != status) { - case ePAR_TYPE_U8: - return (bool) (par_get_u8(par_num) != par_get_config(par_num)->def.u8); - - case ePAR_TYPE_I8: - return (bool) (par_get_i8(par_num) != par_get_config(par_num)->def.i8); - - case ePAR_TYPE_U16: - return (bool) (par_get_u16(par_num) != par_get_config(par_num)->def.u16); - - case ePAR_TYPE_I16: - return (bool) (par_get_i16(par_num) != par_get_config(par_num)->def.i16); - - case ePAR_TYPE_U32: - return (bool) (par_get_u32(par_num) != par_get_config(par_num)->def.u32); - - case ePAR_TYPE_I32: - return (bool) (par_get_i32(par_num) != par_get_config(par_num)->def.i32); - - case ePAR_TYPE_F32: - return (bool) (par_get_f32(par_num) != par_get_config(par_num)->def.f32); - - case ePAR_TYPE_NUM_OF: - default: - PAR_ASSERT( 0 ); - return false; + return status; } - return false; -} - -//////////////////////////////////////////////////////////////////////////////// -/** -* Get parameter configurations -* -* @note In case parameter is not found it return NULL! -* -* @param[in] par_num - Parameter number (enumeration) -* @return Parameter configuration -*/ -//////////////////////////////////////////////////////////////////////////////// -const par_cfg_t * par_get_config(const par_num_t par_num) -{ - // Invalid parameter - PAR_ASSERT( par_num < ePAR_NUM_OF ); - if ( par_num >= ePAR_NUM_OF ) return NULL; - - return (const par_cfg_t*) par_cfg_get(par_num); -} - -//////////////////////////////////////////////////////////////////////////////// -/** -* Get parameter name -* -* @param[in] par_num - Parameter number (enumeration) -* @return Parameter name -*/ -//////////////////////////////////////////////////////////////////////////////// -const char * par_get_name(const par_num_t par_num) -{ - const par_cfg_t * const par_cfg = par_get_config(par_num); - - if ( NULL != par_cfg ) + switch (par_cfg->type) { - return par_cfg->name; + case ePAR_TYPE_U8: + { + uint8_t cur = 0U; + const par_status_t status = par_get_u8(par_num, &cur); + if (ePAR_OK != status) + return status; + *p_value_changed = (cur != *(const uint8_t *)p_val); + break; } - return NULL; -} + case ePAR_TYPE_I8: + { + int8_t cur = 0; + const par_status_t status = par_get_i8(par_num, &cur); + if (ePAR_OK != status) + return status; + *p_value_changed = (cur != *(const int8_t *)p_val); + break; + } -//////////////////////////////////////////////////////////////////////////////// -/** -* Get parameter value range -* -* @param[in] par_num - Parameter number (enumeration) -* @return Parameter min/max range -*/ -//////////////////////////////////////////////////////////////////////////////// -par_range_t par_get_range(const par_num_t par_num) -{ - par_range_t range = {0}; - const par_cfg_t * const par_cfg = par_get_config(par_num); + case ePAR_TYPE_U16: + { + uint16_t cur = 0U; + const par_status_t status = par_get_u16(par_num, &cur); + if (ePAR_OK != status) + return status; + *p_value_changed = (cur != *(const uint16_t *)p_val); + break; + } - if ( NULL != par_cfg ) + case ePAR_TYPE_I16: { - range.min = par_cfg->min; - range.max = par_cfg->max; + int16_t cur = 0; + const par_status_t status = par_get_i16(par_num, &cur); + if (ePAR_OK != status) + return status; + *p_value_changed = (cur != *(const int16_t *)p_val); + break; } - return range; -} + case ePAR_TYPE_U32: + { + uint32_t cur = 0U; + const par_status_t status = par_get_u32(par_num, &cur); + if (ePAR_OK != status) + return status; + *p_value_changed = (cur != *(const uint32_t *)p_val); + break; + } -//////////////////////////////////////////////////////////////////////////////// -/** -* Get parameter unit -* -* @param[in] par_num - Parameter number (enumeration) -* @return Parameter unit -*/ -//////////////////////////////////////////////////////////////////////////////// -const char * par_get_unit(const par_num_t par_num) -{ - const par_cfg_t * const par_cfg = par_get_config(par_num); + case ePAR_TYPE_I32: + { + int32_t cur = 0; + const par_status_t status = par_get_i32(par_num, &cur); + if (ePAR_OK != status) + return status; + *p_value_changed = (cur != *(const int32_t *)p_val); + break; + } - if ( NULL != par_cfg ) +#if (1 == PAR_CFG_ENABLE_TYPE_F32) + case ePAR_TYPE_F32: { - return par_cfg->unit; + float32_t cur = 0.0f; + const par_status_t status = par_get_f32(par_num, &cur); + if (ePAR_OK != status) + return status; + *p_value_changed = !par_f32_bits_equal(cur, *(const float32_t *)p_val); + break; } +#endif - return NULL; -} + case ePAR_TYPE_NUM_OF: + default: + PAR_ASSERT(0); + return ePAR_ERROR_TYPE; + } -//////////////////////////////////////////////////////////////////////////////// + return ePAR_OK; +} /** -* Get parameter description -* -* @param[in] par_num - Parameter number (enumeration) -* @return Parameter description -*/ -//////////////////////////////////////////////////////////////////////////////// -const char * par_get_desc(const par_num_t par_num) + * @brief Set parameter value and save to NVM if value changed. + * + * @note Mandatory to cast input argument to appropriate type. E.g.: + * + * @code + * float32_t my_val = 1.234f;. + * par_set( ePAR_MY_VAR, (float32_t*) &my_val );. + * @endcode + * + * @note Input is the internal parameter number (`par_num_t`) from `par_def.h`, + * not the external parameter ID. + * + * @param par_num Parameter number (enumeration). + * @param p_val Pointer to value. + * @return Status of operation. + */ +par_status_t par_set_n_save(const par_num_t par_num, const void *p_val) { - const par_cfg_t * const par_cfg = par_get_config(par_num); + bool value_change = false; + par_status_t status = par_is_value_changed(par_num, p_val, &value_change); - if ( NULL != par_cfg ) + if (ePAR_OK != status) { - return par_cfg->desc; + PAR_ERR_PRINT("PAR: failed to read current value before set_n_save, par_num=%u status=%s", (unsigned)par_num, par_get_status_str(status)); + PAR_ASSERT(0); + return status; } - return NULL; -} - -//////////////////////////////////////////////////////////////////////////////// -/** -* Get parameter type -* -* @param[in] par_num - Parameter number (enumeration) -* @return Parameter data type -*/ -//////////////////////////////////////////////////////////////////////////////// -par_type_list_t par_get_type(const par_num_t par_num) -{ - const par_cfg_t * const par_cfg = par_get_config(par_num); - - if ( NULL != par_cfg ) + status = par_set(par_num, p_val); + /* Persist only when the value actually changed. */ + if ((ePAR_OK == status) && value_change) { - return par_cfg->type; + PAR_DBG_PRINT("PAR: set_n_save detected value change, par_num=%u", (unsigned)par_num); + status |= par_save(par_num); + } + else if (ePAR_OK == status) + { + PAR_DBG_PRINT("PAR: set_n_save skipped NVM write because value is unchanged, par_num=%u", (unsigned)par_num); } - return ePAR_TYPE_NUM_OF; + return status; } - -//////////////////////////////////////////////////////////////////////////////// /** -* Get parameter access (RO, RW) -* -* @param[in] par_num - Parameter number (enumeration) -* @return Parameter access -*/ -//////////////////////////////////////////////////////////////////////////////// -par_access_t par_get_access(const par_num_t par_num) + * @brief Store all parameters value to NVM. + * + * @pre NVM storage must be initialized first and "PAR_CFG_NVM_EN" + * settings must be enabled. + * + * @return Status of operation. + */ +par_status_t par_save_all(void) { - const par_cfg_t * const par_cfg = par_get_config(par_num); - - if ( NULL != par_cfg ) - { - return par_cfg->access; - } + PAR_ASSERT(true == par_is_init()); + if (true != par_is_init()) + return ePAR_ERROR_INIT; - return ePAR_ACCESS_RO; + PAR_DBG_PRINT("PAR: persisting all configured parameters to NVM"); + return par_nvm_write_all(); } - -//////////////////////////////////////////////////////////////////////////////// /** -* Is parameter persistant (does it stores to NVM) -* -* @param[in] par_num - Parameter number (enumeration) -* @return True if parameter persistant -*/ -//////////////////////////////////////////////////////////////////////////////// -bool par_is_persistant(const par_num_t par_num) + * @brief Store single parameter value to NVM. + * + * @pre NVM storage must be initialized first and "PAR_CFG_NVM_EN" + * settings must be enabled. + * + * @param par_num Parameter number (enumeration). + * @return Status of operation. + */ +par_status_t par_save(const par_num_t par_num) { - const par_cfg_t * const par_cfg = par_get_config(par_num); - - if ( NULL != par_cfg ) - { - return par_cfg->persistant; - } + PAR_ASSERT(true == par_is_init()); + if (true != par_is_init()) + return ePAR_ERROR_INIT; - return false; + PAR_DBG_PRINT("PAR: persisting par_num=%u to NVM", (unsigned)par_num); + return par_nvm_write(par_num, true); } - -//////////////////////////////////////////////////////////////////////////////// /** -* Get parameter number (enumeration) by ID -* -* @param[in] id - Parameter ID -* @param[out] p_par_num - Pointer to parameter enumeration number -* @return status - Status of operation -*/ -//////////////////////////////////////////////////////////////////////////////// -par_status_t par_get_num_by_id(const uint16_t id, par_num_t * const p_par_num) + * @brief Store single parameter value to NVM by its ID value. + * + * @pre NVM storage must be initialized first and "PAR_CFG_NVM_EN" + * settings must be enabled. + * + * @code + * // Use case. + * // Store par from ID 10 to 32. + * uint8_t par_id;. + * + * for ( par_id = 10; par_id < 32; par_id++ ). + * {. + * status |= par_save_by_id( par_id ). + * }. + * + * @endcode + * + * @param par_id Parameter ID number. + * @return Status of operation. + */ +#if (1 == PAR_CFG_ENABLE_ID) +par_status_t par_save_by_id(const uint16_t par_id) { - if ( NULL != p_par_num ) - { - for (par_num_t par_num = 0; par_num < ePAR_NUM_OF; par_num++ ) - { - const par_cfg_t * const par_cfg = par_get_config(par_num); + par_num_t par_num = 0; + PAR_ASSERT(true == par_is_init()); + if (true != par_is_init()) + return ePAR_ERROR_INIT; - if (( NULL != par_cfg ) && ( id == par_cfg->id )) - { - *p_par_num = par_num; - return ePAR_OK; - } - } + if (ePAR_OK == par_get_num_by_id(par_id, &par_num)) + { + return par_save(par_num); } + PAR_WARN_PRINT("PAR: persist-by-id could not resolve parameter id=%u", (unsigned)par_id); return ePAR_ERROR; } - -//////////////////////////////////////////////////////////////////////////////// +#endif /** -* Get parameter ID by number (enumeration) -* -* @param[in] par_num - Parameter number -* @param[out] p_id - Pointer to parameter ID -* @return status - Status of operation -*/ -//////////////////////////////////////////////////////////////////////////////// -par_status_t par_get_id_by_num(const par_num_t par_num, uint16_t * const p_id) + * @brief Clean all stored parameters inside NVM. + * + * @note This function shall be locked as it will erase complete parameter. + * region of NVM space. Shall be used only during. + * + * @pre NVM storage must be initialized first and "PAR_CFG_NVM_EN" + * settings must be enabled. + * + * @return Status of operation. + */ +par_status_t par_save_clean(void) { - if ( NULL != p_id ) - { - const par_cfg_t * const par_cfg = par_get_config(par_num); - - if ( NULL != par_cfg ) - { - *p_id = par_cfg->id; - return ePAR_OK; - } - } + PAR_ASSERT(true == par_is_init()); + if (true != par_is_init()) + return ePAR_ERROR_INIT; - return ePAR_ERROR; + return par_nvm_reset_all(); } -#if ( 1 == PAR_CFG_NVM_EN ) - //////////////////////////////////////////////////////////////////////////////// - /** - * Set parameter value and save to NVM if value changed - * - * @note Mandatory to cast input argument to appropriate type. E.g.: - * - * @code - * float32_t my_val = 1.234f; - * par_set( ePAR_MY_VAR, (float32_t*) &my_val ); - * @endcode - * - * @note Input is parameter number (enumeration) defined in par_cfg.h and not - * parameter ID number! - * - * @param[in] par_num - Parameter number (enumeration) - * @param[in] p_val - Pointer to value - * @return status - Status of operation - */ - //////////////////////////////////////////////////////////////////////////////// - par_status_t par_set_n_save(const par_num_t par_num, const void * p_val) - { - // Check if parameter value is about to change - const bool value_change = par_is_value_changed( par_num, p_val ); - - // Set parameter - par_status_t status = par_set(par_num, p_val); - - // Parameter set OK and value has been changed -> makes sense to store to NVM - if (( ePAR_OK == status ) && value_change ) - { - status |= par_save(par_num); - } - - return status; - } - - //////////////////////////////////////////////////////////////////////////////// - /** - * Store all parameters value to NVM - * - * @pre NVM storage must be initialized first and "PAR_CFG_NVM_EN" - * settings must be enabled. - * - * @return status - Status of operation - */ - //////////////////////////////////////////////////////////////////////////////// - par_status_t par_save_all(void) - { - // Check initialization - PAR_ASSERT( true == par_is_init()); - if ( true != par_is_init()) return ePAR_ERROR_INIT; - - return par_nvm_write_all(); - } - - //////////////////////////////////////////////////////////////////////////////// - /** - * Store single parameter value to NVM - * - * @pre NVM storage must be initialized first and "PAR_CFG_NVM_EN" - * settings must be enabled. - * - * @param[in] par_num - Parameter number (enumeration) - * @return status - Status of operation - */ - //////////////////////////////////////////////////////////////////////////////// - par_status_t par_save(const par_num_t par_num) - { - // Check initialization - PAR_ASSERT( true == par_is_init()); - if ( true != par_is_init()) return ePAR_ERROR_INIT; - - return par_nvm_write( par_num, true ); - } - - //////////////////////////////////////////////////////////////////////////////// - /** - * Store single parameter value to NVM by its ID value - * - * @pre NVM storage must be initialized first and "PAR_CFG_NVM_EN" - * settings must be enabled. - * - * @code - * // Use case - * // Store par from ID 10 to 32 - * uint8_t par_id; - * - * for ( par_id = 10; par_id < 32; par_id++ ) - * { - * status |= par_save_by_id( par_id ) - * } - * - * @endcode - * - * @param[in] par_id - Parameter ID number - * @return status - Status of operation - */ - //////////////////////////////////////////////////////////////////////////////// - par_status_t par_save_by_id(const uint16_t par_id) - { - par_num_t par_num = 0; - - // Check initialization - PAR_ASSERT( true == par_is_init()); - if ( true != par_is_init()) return ePAR_ERROR_INIT; - - if ( ePAR_OK == par_get_num_by_id( par_id, &par_num )) - { - return par_save( par_num ); - } - - return ePAR_ERROR; - } - - //////////////////////////////////////////////////////////////////////////////// - /** - * Clean all stored parameters inside NVM - * - * @note This function shall be locked as it will erase complete parameter - * region of NVM space. Shall be used only during - * - * @pre NVM storage must be initialized first and "PAR_CFG_NVM_EN" - * settings must be enabled. - * - * @return status - Status of operation - */ - //////////////////////////////////////////////////////////////////////////////// - par_status_t par_save_clean(void) - { - // Check initialization - PAR_ASSERT( true == par_is_init()); - if ( true != par_is_init()) return ePAR_ERROR_INIT; - - return par_nvm_reset_all(); - } - #endif - -//////////////////////////////////////////////////////////////////////////////// /** -* Register parameter on change callback -* -* @param[in] par_num - Parameter number (enumeration) -* @param[in] cb - Callback -* @return void -*/ -//////////////////////////////////////////////////////////////////////////////// + * @brief Register parameter on change callback. + * + * @param par_num Parameter number (enumeration). + * @param cb Callback. + */ +#if (1 == PAR_CFG_ENABLE_CHANGE_CALLBACK) void par_register_on_change_cb(const par_num_t par_num, const pf_par_on_change_cb_t cb) { - PAR_ASSERT( par_num < ePAR_NUM_OF ); + PAR_ASSERT(par_num < ePAR_NUM_OF); g_par_cb_table[par_num].on_change = cb; } - -//////////////////////////////////////////////////////////////////////////////// +#endif /** -* Register parameter value validation function -* -* @param[in] par_num - Parameter number (enumeration) -* @param[in] validation - Validation -* @return void -*/ -//////////////////////////////////////////////////////////////////////////////// + * @brief Register parameter value validation function. + * + * @param par_num Parameter number (enumeration). + * @param validation Validation. + */ +#if (1 == PAR_CFG_ENABLE_RUNTIME_VALIDATION) void par_register_validation(const par_num_t par_num, const pf_par_validation_t validation) { - PAR_ASSERT( par_num < ePAR_NUM_OF ); + PAR_ASSERT(par_num < ePAR_NUM_OF); g_par_cb_table[par_num].validation = validation; } +#endif -#if ( PAR_CFG_DEBUG_EN ) +#if (PAR_CFG_DEBUG_EN) +/** + * @brief Get status string description. + * + * @param status Parameter status. + * @return Parameter status description. + */ +const char *par_get_status_str(const par_status_t status) +{ + uint8_t i = 0; + const char *str = "N/A"; - //////////////////////////////////////////////////////////////////////////////// - /** - * Get status string description - * - * @param[in] status - Parameter status - * @return str - Parameter status description - */ - //////////////////////////////////////////////////////////////////////////////// - const char * par_get_status_str(const par_status_t status) + if (ePAR_OK == status) { - uint8_t i = 0; - const char * str = "N/A"; - - if ( ePAR_OK == status ) - { - str = (const char*) gs_status[0]; - } - else + str = (const char *)gs_status[0]; + } + else + { + for (i = 0; i < 16; i++) { - for ( i = 0; i < 16; i++ ) + if (status & (1 << i)) { - if ( status & ( 1< -*/ -//////////////////////////////////////////////////////////////////////////////// + * @} + */ diff --git a/src/par.h b/src/par.h index 861cb4f..55902be 100644 --- a/src/par.h +++ b/src/par.h @@ -1,284 +1,750 @@ -// Copyright (c) 2026 Ziga Miklosic -// All Rights Reserved -// This software is under MIT licence (https://opensource.org/licenses/MIT) -//////////////////////////////////////////////////////////////////////////////// -/** -*@file par.h -*@brief Device parameters API functions -*@author Ziga Miklosic -*@email ziga.miklosic@gmail.com -*@author Matej Otic -*@email otic.matej@dancing-bits.com -*@date 29.01.2026 -*@version V3.0.1 -*/ -//////////////////////////////////////////////////////////////////////////////// -/** -*@addtogroup PARAMETERS_API -* @{ -*/ -//////////////////////////////////////////////////////////////////////////////// +/** + * @file par.h + * @brief Declare the public device-parameter API. + * @author Ziga Miklosic + * @version V3.0.1 + * @date 2026-01-29 + * + * @copyright Copyright (c) 2026 Ziga Miklosic. Distributed under the MIT license. + * + * @note : + * @par Change Log: + * Date Version Author Description + * 2026-01-29 V3.0.1 Ziga Miklosic first version + */ +/** + * @addtogroup PARAMETERS_API + * @{ + */ #ifndef _PAR_H_ #define _PAR_H_ - -//////////////////////////////////////////////////////////////////////////////// -// Includes -//////////////////////////////////////////////////////////////////////////////// +/** + * @brief Include dependencies. + */ #include #include #include -#include "../../par_cfg.h" - -//////////////////////////////////////////////////////////////////////////////// -// Definitions -//////////////////////////////////////////////////////////////////////////////// - +#include "par_cfg.h" /** - * Module version + * @brief Compile-time definitions. */ -#define PAR_VER_MAJOR ( 3 ) -#define PAR_VER_MINOR ( 0 ) -#define PAR_VER_DEVELOP ( 1 ) +/** + * @brief Module version. + */ +#define PAR_VER_MAJOR (3) +#define PAR_VER_MINOR (0) +#define PAR_VER_DEVELOP (2) /** - * Parameter status + * @brief Parameter status. */ -enum +typedef enum { - ePAR_OK = 0U, /** set PAR_CFG_NVM_EN to 0 */ - ePAR_WAR_LIMITED = 0x0800U, /** set PAR_CFG_NVM_EN to 0. */ + ePAR_WAR_LIMITED = 0x4000U, /**< Parameter value limited within [min,max]. */ +} par_status_t; /** - * Parameters type enumeration + * @brief Parameters type enumeration. */ -enum +typedef enum { - ePAR_TYPE_U8 = 0, /** -*/ -//////////////////////////////////////////////////////////////////////////////// + * @} + */ -#endif // _PAR_H_ +#endif /* _PAR_H_ */ diff --git a/src/par_cfg.h b/src/par_cfg.h new file mode 100644 index 0000000..35119ec --- /dev/null +++ b/src/par_cfg.h @@ -0,0 +1,540 @@ +/** + * @file par_cfg.h + * @brief Provide compile-time configuration for the parameter module. + * @author Ziga Miklosic + * @version 1.0 + * @date 2026-01-29 + * + * @copyright Copyright (c) 2026 Ziga Miklosic. Distributed under the MIT license. + * + * @note : + * @par Change Log: + * Date Version Author Description + * 2026-01-29 1.0 Ziga Miklosic first version + */ +/** + * @addtogroup PAR_CFG + * @{ + * + * @brief Configuration for device parameters. + */ + +#ifndef _PAR_CFG_H_ +#define _PAR_CFG_H_ +/** + * @brief Include dependencies. + */ +#include +#include +#include +#include "def/par_def.h" + +/** + * @brief USER CODE BEGIN... + */ + +/** + * @brief Platform adaptation bridge. + * + * @note Port layer may override default PAR_CFG_* settings. + * + * @note This header is included unconditionally. + * Integrator shall provide "par_cfg_port.h" in include path. + * If no platform override is needed, create an empty stub header. + * with include guard (for example in port/par_cfg_port.h). + */ +#include "par_cfg_port.h" + +/** + * @brief USER CODE END... + */ +/** + * @brief Compile-time definitions. + */ +#include "persist/par_nvm_cfg.h" + +/** + * @brief Enable/Disable debug mode. + */ +#ifndef PAR_CFG_DEBUG_EN +#define PAR_CFG_DEBUG_EN (1) +#ifndef DEBUG +#undef PAR_CFG_DEBUG_EN +#define PAR_CFG_DEBUG_EN (0) +#endif +#endif + +/** + * @brief Enable/Disable assertions. + */ +#ifndef PAR_CFG_ASSERT_EN +#define PAR_CFG_ASSERT_EN (1) +#ifndef DEBUG +#undef PAR_CFG_ASSERT_EN +#define PAR_CFG_ASSERT_EN (0) +#endif +#endif + +/** + * @brief Platform hook fallbacks. + */ +#ifndef PAR_PORT_ASSERT +#define PAR_PORT_ASSERT(x) \ + do \ + { \ + (void)(x); \ + } while (0) +#endif + +#ifndef PAR_PORT_LOG_INFO +#define PAR_PORT_LOG_INFO(...) \ + do \ + { \ + } while (0) +#endif + +#ifndef PAR_PORT_LOG_DEBUG +#define PAR_PORT_LOG_DEBUG(...) \ + do \ + { \ + } while (0) +#endif + +#ifndef PAR_PORT_LOG_WARN +#define PAR_PORT_LOG_WARN(...) \ + do \ + { \ + } while (0) +#endif + +#ifndef PAR_PORT_LOG_ERROR +#define PAR_PORT_LOG_ERROR(...) \ + do \ + { \ + } while (0) +#endif + +#ifndef PAR_PORT_STATIC_ASSERT +#define PAR_PORT_STATIC_ASSERT(name, expn) typedef char _static_assert_##name[(expn) ? 1 : -1] +#endif + +/** + * @brief Platform weak symbol macro. + * + * @note Integrator may override this macro (for example: RT_WEAK). + */ +#ifndef PAR_PORT_WEAK +#define PAR_PORT_WEAK __attribute__((weak)) +#endif + +/** + * @brief Type alignment abstraction. + * + * @note Default uses C11 _Alignof. If unavailable, falls back to. + * offsetof-based alignment calculation. + * @note This abstraction is intended for ordinary object types and platform. + * atomic-wrapper types, but the platform must guarantee the expression. + * is valid for its custom atomic wrapper definitions. + */ +#ifndef PAR_ALIGNOF +#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) +#define PAR_ALIGNOF(type) _Alignof(type) +#else +#define PAR_ALIGNOF(type) offsetof(struct { char _par_align_c; type _par_align_t; }, _par_align_t) +#endif +#endif + +/** + * @brief Package compile-time assert. + */ +#define PAR_STATIC_ASSERT(name, expn) PAR_PORT_STATIC_ASSERT(name, expn); +/** + * @brief Resolve log/assert routing mode before default macro emission. + */ +#if !defined(PAR_CFG_PORT_HOOK_EN) +#define PAR_CFG_USE_PORT_HOOKS (0) +#elif (1 == PAR_CFG_PORT_HOOK_EN) +#define PAR_CFG_USE_PORT_HOOKS (1) +#else +#define PAR_CFG_USE_PORT_HOOKS (0) +#endif + +/** + * @brief Debug communication port macros. + */ +#if (0 == PAR_CFG_USE_PORT_HOOKS) +#if (1 == PAR_CFG_DEBUG_EN) +#ifndef PAR_CFG_DIRECT_LOG +#define PAR_CFG_DIRECT_LOG(...) (cli_printf((char *)__VA_ARGS__)) +#endif +#ifndef PAR_CFG_DIRECT_INFO_LOG +#define PAR_CFG_DIRECT_INFO_LOG(...) PAR_CFG_DIRECT_LOG(__VA_ARGS__) +#endif +#ifndef PAR_CFG_DIRECT_DEBUG_LOG +#define PAR_CFG_DIRECT_DEBUG_LOG(...) PAR_CFG_DIRECT_LOG(__VA_ARGS__) +#endif +#ifndef PAR_CFG_DIRECT_WARN_LOG +#define PAR_CFG_DIRECT_WARN_LOG(...) PAR_CFG_DIRECT_LOG(__VA_ARGS__) +#endif +#ifndef PAR_CFG_DIRECT_ERROR_LOG +#define PAR_CFG_DIRECT_ERROR_LOG(...) PAR_CFG_DIRECT_LOG(__VA_ARGS__) +#endif +#define PAR_INFO_PRINT(...) PAR_CFG_DIRECT_INFO_LOG(__VA_ARGS__) +#define PAR_DBG_PRINT(...) PAR_CFG_DIRECT_DEBUG_LOG(__VA_ARGS__) +#define PAR_WARN_PRINT(...) PAR_CFG_DIRECT_WARN_LOG(__VA_ARGS__) +#define PAR_ERR_PRINT(...) PAR_CFG_DIRECT_ERROR_LOG(__VA_ARGS__) +#else +#define PAR_INFO_PRINT(...) \ + { \ + ; \ + } +#define PAR_DBG_PRINT(...) \ + { \ + ; \ + } +#define PAR_WARN_PRINT(...) \ + { \ + ; \ + } +#define PAR_ERR_PRINT(...) \ + { \ + ; \ + } +#endif +#else +#if (1 == PAR_CFG_DEBUG_EN) +#define PAR_INFO_PRINT(...) PAR_PORT_LOG_INFO(__VA_ARGS__) +#define PAR_DBG_PRINT(...) PAR_PORT_LOG_DEBUG(__VA_ARGS__) +#define PAR_WARN_PRINT(...) PAR_PORT_LOG_WARN(__VA_ARGS__) +#define PAR_ERR_PRINT(...) PAR_PORT_LOG_ERROR(__VA_ARGS__) +#else +#define PAR_INFO_PRINT(...) \ + { \ + ; \ + } +#define PAR_DBG_PRINT(...) \ + { \ + ; \ + } +#define PAR_WARN_PRINT(...) \ + { \ + ; \ + } +#define PAR_ERR_PRINT(...) \ + { \ + ; \ + } +#endif +#endif + +/** + * @brief Assertion macros. + */ +#if (0 == PAR_CFG_USE_PORT_HOOKS) +#if (1 == PAR_CFG_ASSERT_EN) +#ifndef PAR_CFG_DIRECT_ASSERT +#ifdef PROJ_CFG_ASSERT +#define PAR_CFG_DIRECT_ASSERT(x) PROJ_CFG_ASSERT(x) +#else +#define PAR_CFG_DIRECT_ASSERT(x) \ + do \ + { \ + (void)(x); \ + } while (0) +#endif +#endif +#define PAR_ASSERT(x) PAR_CFG_DIRECT_ASSERT(x) +#else +#define PAR_ASSERT(x) \ + { \ + ; \ + } +#endif +#else +#if (1 == PAR_CFG_ASSERT_EN) +#define PAR_ASSERT(x) PAR_PORT_ASSERT(x) +#else +#define PAR_ASSERT(x) \ + { \ + ; \ + } +#endif +#endif + +#undef PAR_CFG_USE_PORT_HOOKS + +/** + * @brief Invalid configuration catcher. + * + * @note Shall be intact by end user! + */ +#if (0 == PAR_CFG_NVM_EN) && (1 == PAR_CFG_TABLE_ID_CHECK_EN) +#error "Parameter settings invalid: Disable table ID checking (PAR_CFG_TABLE_ID_CHECK_EN)!" +#endif + +/** + * @brief Extended package configurations (non-template additions). + */ +#ifndef PAR_CFG_MUTEX_EN +#define PAR_CFG_MUTEX_EN (1) +#endif + +/** + * @brief Parameter mutex timeout. + * @details Unit: ms. + */ +#ifndef PAR_CFG_MUTEX_TIMEOUT_MS +#define PAR_CFG_MUTEX_TIMEOUT_MS (10) +#endif + +/** + * @brief Enable/Disable port-specific par_if backend. + */ +#ifndef PAR_CFG_IF_PORT_EN +#define PAR_CFG_IF_PORT_EN (0) +#endif + +/** + * @brief Enable/Disable port hooks for log/assert. + */ +#ifndef PAR_CFG_PORT_HOOK_EN +#define PAR_CFG_PORT_HOOK_EN (0) +#endif + +/** + * @brief Parameter storage layout source. + */ +#define PAR_CFG_LAYOUT_COMPILE_SCAN (0u) +#define PAR_CFG_LAYOUT_SCRIPT (1u) + +/** + * @brief Select parameter storage layout source. + * + * @note + * - COMPILE_SCAN: counts are compile-time constants, offsets are scanned in init. + * - SCRIPT : counts/offsets are provided by generated static layout header. + */ +#ifndef PAR_CFG_LAYOUT_SOURCE +#define PAR_CFG_LAYOUT_SOURCE PAR_CFG_LAYOUT_COMPILE_SCAN +#endif + +/** + * @brief Static layout include path. + * + * @note Can be overridden by integrator to include generated layout header. + */ +#ifndef PAR_CFG_LAYOUT_STATIC_INCLUDE +#define PAR_CFG_LAYOUT_STATIC_INCLUDE "par_layout_static.h" +#endif + +#ifndef PAR_CFG_ENABLE_TYPE_F32 +#define PAR_CFG_ENABLE_TYPE_F32 (1) +#endif + +/** + * @brief Enable/Disable runtime validation callbacks in normal setters. + */ +#ifndef PAR_CFG_ENABLE_RUNTIME_VALIDATION +#define PAR_CFG_ENABLE_RUNTIME_VALIDATION (1) +#endif + +/** + * @brief Enable/Disable on-change callbacks in normal setters. + */ +#ifndef PAR_CFG_ENABLE_CHANGE_CALLBACK +#define PAR_CFG_ENABLE_CHANGE_CALLBACK (1) +#endif + +/** + * @brief Enable/Disable raw reset-all API and default mirror storage. + * + * @note When enabled, module compiles par_reset_all_to_default_raw() and. + * keeps a grouped default mirror snapshot for raw restore. + * + * @note The speedup comes from bypassing per-parameter setter-side logic such. + * as runtime validation, change callback, and range handling. + * + * @note The raw reset path restores parameter storage directly from that. + * grouped default mirror snapshot, so it is typically faster than. + * par_set_all_to_default(), which resets parameters one by one through. + * the normal runtime setter path. + */ +#ifndef PAR_CFG_ENABLE_RESET_ALL_RAW +#define PAR_CFG_ENABLE_RESET_ALL_RAW (1) +#endif + +/** + * @brief Enable/Disable parameter range metadata (min/max). + */ +#ifndef PAR_CFG_ENABLE_RANGE +#define PAR_CFG_ENABLE_RANGE (1) +#endif + +/** + * @brief Enable/Disable parameter name metadata. + */ +#ifndef PAR_CFG_ENABLE_NAME +#define PAR_CFG_ENABLE_NAME (1) +#endif + +/** + * @brief Enable/Disable parameter unit metadata. + */ +#ifndef PAR_CFG_ENABLE_UNIT +#define PAR_CFG_ENABLE_UNIT (1) +#endif + +/** + * @brief Enable/Disable parameter description metadata. + */ +#ifndef PAR_CFG_ENABLE_DESC +#define PAR_CFG_ENABLE_DESC (1) +#endif + +/** + * @brief Enable/Disable parameter ID metadata. + */ +#ifndef PAR_CFG_ENABLE_ID +#define PAR_CFG_ENABLE_ID (1) +#endif + +#if (1 == PAR_CFG_ENABLE_ID) +/** + * @brief Enable/Disable optional runtime duplicate-ID diagnostic scan. + * + * @note Static ID-map generation and compile-time duplicate-ID checking remain. + * enabled by default whenever PAR_CFG_ENABLE_ID = 1. + */ +#ifndef PAR_CFG_ENABLE_RUNTIME_ID_DUP_CHECK +#define PAR_CFG_ENABLE_RUNTIME_ID_DUP_CHECK (0) +#endif + +/** + * @brief Enable/Disable optional runtime ID hash-collision diagnostic scan. + * + * @note Static ID-map generation and compile-time hash collision checking. + * remain enabled by default whenever PAR_CFG_ENABLE_ID = 1. + */ +#ifndef PAR_CFG_ENABLE_RUNTIME_ID_HASH_COLLISION_CHECK +#define PAR_CFG_ENABLE_RUNTIME_ID_HASH_COLLISION_CHECK (0) +#endif + +/** + * @brief Internal-only ID hash geometry helpers. + * + * @note Shared by the compile-time table checks, compile-time static ID-map. + * generation, and optional runtime diagnostic scans. + * + * @note These helpers define the internal geometry of the core ID lookup map. + * Integrators should not treat them as a stable public extension API. + */ +#ifndef PAR_ID_HASH_GOLDEN_RATIO_32 +#define PAR_ID_HASH_GOLDEN_RATIO_32 (0x61C88647u) +#endif + +#ifndef PAR_ID_HASH_MIN_BUCKETS +#define PAR_ID_HASH_MIN_BUCKETS ((uint32_t)(2u * (uint32_t)ePAR_NUM_OF)) +#endif + +#ifndef PAR_ID_HASH_BITS_FROM_MIN_BUCKETS +#define PAR_ID_HASH_BITS_FROM_MIN_BUCKETS(min_buckets_) \ + (((min_buckets_) <= (1u << 1)) ? 1u : ((min_buckets_) <= (1u << 2)) ? 2u \ + : ((min_buckets_) <= (1u << 3)) ? 3u \ + : ((min_buckets_) <= (1u << 4)) ? 4u \ + : ((min_buckets_) <= (1u << 5)) ? 5u \ + : ((min_buckets_) <= (1u << 6)) ? 6u \ + : ((min_buckets_) <= (1u << 7)) ? 7u \ + : ((min_buckets_) <= (1u << 8)) ? 8u \ + : ((min_buckets_) <= (1u << 9)) ? 9u \ + : ((min_buckets_) <= (1u << 10)) ? 10u \ + : ((min_buckets_) <= (1u << 11)) ? 11u \ + : ((min_buckets_) <= (1u << 12)) ? 12u \ + : ((min_buckets_) <= (1u << 13)) ? 13u \ + : ((min_buckets_) <= (1u << 14)) ? 14u \ + : ((min_buckets_) <= (1u << 15)) ? 15u \ + : ((min_buckets_) <= (1u << 16)) ? 16u \ + : ((min_buckets_) <= (1u << 17)) ? 17u \ + : 18u) +#endif + +#ifndef PAR_ID_HASH_BITS +#define PAR_ID_HASH_BITS PAR_ID_HASH_BITS_FROM_MIN_BUCKETS(PAR_ID_HASH_MIN_BUCKETS) +#endif + +#ifndef PAR_ID_HASH_SIZE +#define PAR_ID_HASH_SIZE (1u << PAR_ID_HASH_BITS) +#endif + +#ifndef PAR_HASH_ID_CONST +#define PAR_HASH_ID_CONST(id_) ((((uint32_t)(id_)) * PAR_ID_HASH_GOLDEN_RATIO_32) >> (32u - PAR_ID_HASH_BITS)) +#endif + +PAR_STATIC_ASSERT(par_id_hash_size_valid, (PAR_ID_HASH_SIZE >= PAR_ID_HASH_MIN_BUCKETS)); +PAR_STATIC_ASSERT(par_id_hash_bits_valid, ((PAR_ID_HASH_BITS > 0u) && (PAR_ID_HASH_BITS < 32u))); +#endif + +/** + * @brief Enable/Disable parameter access metadata. + */ +#ifndef PAR_CFG_ENABLE_ACCESS +#define PAR_CFG_ENABLE_ACCESS (1) +#endif + +/** + * @brief Enable/Disable optional external role-policy metadata and enforcement. + */ +#ifndef PAR_CFG_ENABLE_ROLE_POLICY +#define PAR_CFG_ENABLE_ROLE_POLICY (0) +#endif + +/** + * @brief Enable/Disable description check. + * + * @note Default follows PAR_CFG_ENABLE_DESC. + */ +#ifndef PAR_CFG_ENABLE_DESC_CHECK +#define PAR_CFG_ENABLE_DESC_CHECK (PAR_CFG_ENABLE_DESC) +#endif + +/** + * @brief Configuration dependency checks for optional fields/features. + */ +#if (1 == PAR_CFG_NVM_EN) && (0 == PAR_CFG_ENABLE_ID) && \ + (PAR_CFG_NVM_RECORD_LAYOUT != PAR_CFG_NVM_RECORD_LAYOUT_FIXED_PAYLOAD_ONLY) && \ + (PAR_CFG_NVM_RECORD_LAYOUT != PAR_CFG_NVM_RECORD_LAYOUT_GROUPED_PAYLOAD_ONLY) +#error "Parameter settings invalid: selected NVM layout requires PAR_CFG_ENABLE_ID = 1!" +#endif + +#if (1 == PAR_CFG_NVM_EN) && \ + ((PAR_CFG_NVM_RECORD_LAYOUT == PAR_CFG_NVM_RECORD_LAYOUT_FIXED_PAYLOAD_ONLY) || \ + (PAR_CFG_NVM_RECORD_LAYOUT == PAR_CFG_NVM_RECORD_LAYOUT_GROUPED_PAYLOAD_ONLY)) && \ + (0 == PAR_CFG_TABLE_ID_CHECK_EN) +#error "Parameter settings invalid: payload-only NVM layouts require PAR_CFG_TABLE_ID_CHECK_EN = 1!" +#endif + + +#if (0 == PAR_CFG_ENABLE_ID) && (1 == PAR_CFG_ENABLE_RUNTIME_ID_DUP_CHECK) +#error "Parameter settings invalid: runtime duplicate-ID diagnostics require PAR_CFG_ENABLE_ID = 1!" +#endif + +#if (0 == PAR_CFG_ENABLE_ID) && (1 == PAR_CFG_ENABLE_RUNTIME_ID_HASH_COLLISION_CHECK) +#error "Parameter settings invalid: runtime ID hash-collision diagnostics require PAR_CFG_ENABLE_ID = 1!" +#endif + +#if (PAR_CFG_LAYOUT_SOURCE != PAR_CFG_LAYOUT_COMPILE_SCAN) && (PAR_CFG_LAYOUT_SOURCE != PAR_CFG_LAYOUT_SCRIPT) +#error "Parameter settings invalid: PAR_CFG_LAYOUT_SOURCE must be PAR_CFG_LAYOUT_COMPILE_SCAN or PAR_CFG_LAYOUT_SCRIPT!" +#endif + +#define PAR_UINT16_MAX (65535u) +/** + * @brief Function declarations. + */ +const par_cfg_t *par_cfg_get_table(void); +const par_cfg_t *par_cfg_get(const par_num_t par_num); +/** + * @brief Return the number of configuration entries. + * @return Configuration table size. + */ +uint32_t par_cfg_get_table_size(void); /**< @} */ + +#endif /* _PAR_CFG_H_ */ diff --git a/src/par_nvm.c b/src/par_nvm.c deleted file mode 100644 index 19465bc..0000000 --- a/src/par_nvm.c +++ /dev/null @@ -1,1107 +0,0 @@ -// Copyright (c) 2026 Ziga Miklosic -// All Rights Reserved -// This software is under MIT licence (https://opensource.org/licenses/MIT) -//////////////////////////////////////////////////////////////////////////////// -/** -*@file par_nvm.c -*@brief Parameter storage to non-volatile memory -*@author Ziga Miklosic -*@email ziga.miklosic@gmail.com -*@date 29.01.2026 -*@version V3.0.1 -*/ -//////////////////////////////////////////////////////////////////////////////// -/** -*@addtogroup PAR_NVM -* @{ -* -* Parameter storage to non-volatile memory handling. -* -* -* @pre NVM module shall have memory region called "Parameters". -* -* NVM module must be initialized before calling any of following -* functions. -* -* @brief This module is responsible for parameter NVM object creation and -* storage manipulation. NVM parameter object consist of it's value -* and a CRC value for validation purposes. -* -* Parameter storage is reserved in "Parameters" region of NVM. Look -* at the nvm_cfg.h/c module for NVM region descriptions. -* -* Parameters stored into NVM in little endianness format. -* -* For details how parameters are handled in NVM go look at the -* documentation. -* -* @note RULES OF "PAR_CFG_TABLE_ID_CHECK_EN" SETTINGS: -* -* It is normal that parameter table will change during development -* and therefore code will detect table change between "RAM" and "NVM" -* (detection of par table change enable/disable with -* "PAR_CFG_TABLE_ID_CHECK_EN" setting). -* -* But as SW is released and potential at customer, developer must not -* change the pre-existing parameter table as it will loose all values -* stored inside NVM. Therefore it is recommended to disable table ID -* detection after first release of SW. Adding new parameters to pre-existing -* table has no harm at all nor does it have any side effects. -*/ -//////////////////////////////////////////////////////////////////////////////// - -//////////////////////////////////////////////////////////////////////////////// -// Includes -//////////////////////////////////////////////////////////////////////////////// -#include "par_nvm.h" -#include "../../par_cfg.h" -#include "../../par_if.h" - -#if ( 1 == PAR_CFG_NVM_EN ) - - #include - #include - - #include "middleware/nvm/nvm/src/nvm.h" - - /** - * Check NVM module compatibility - */ - _Static_assert( 2 == NVM_VER_MAJOR ); - _Static_assert( 1 <= NVM_VER_MINOR ); - - //////////////////////////////////////////////////////////////////////////////// - // Definitions - //////////////////////////////////////////////////////////////////////////////// - - /** - * Parameter signature and size in bytes - */ - #define PAR_NVM_SIGN ( 0xFF00AA55 ) - #define PAR_NVM_SIGN_SIZE ( 4U ) - - /** - * Parameter header number of object size - * - * Unit: byte - */ - #define PAR_NVM_NB_OF_OBJ_SIZE ( 2U ) - - /** - * Parameter CRC size - * - * Unit: byte - */ - #define PAR_NVM_CRC_SIZE ( 2U ) - - /** - * Parameter configuration hash size - * - * Unit: byte - */ - #define PAR_NVM_HASH_SIZE ( 32U ) - - /** - * Parameter NVM header content address start - * - * @note This is offset to reserved NVM region. For absolute address - * add that value to NVM start region. - */ - #define PAR_NVM_HEAD_ADDR ( 0x00 ) - #define PAR_NVM_HEAD_SIGN_ADDR ( PAR_NVM_HEAD_ADDR ) - #define PAR_NVM_HEAD_NB_OF_OBJ_ADDR ( PAR_NVM_HEAD_SIGN_ADDR + PAR_NVM_SIGN_SIZE ) - #define PAR_NVM_HEAD_CRC_ADDR ( PAR_NVM_HEAD_NB_OF_OBJ_ADDR + PAR_NVM_NB_OF_OBJ_SIZE ) - #define PAR_NVM_HEAD_HASH_ADDR ( PAR_NVM_HEAD_CRC_ADDR + PAR_NVM_CRC_SIZE ) - - /** - * Parameters first data object start address - * - * Unit: byte - */ - #define PAR_NVM_FIRST_DATA_OBJ_ADDR ( PAR_NVM_HEAD_HASH_ADDR + PAR_NVM_HASH_SIZE ) - - /** - * Parameter NVM header object - */ - typedef struct - { - uint32_t sign; /** 0 ); - - for (uint8_t i = 0; i < size; i++) - { - crc16 = ( crc16 ^ ( p_data[i] << 8U )); - - for (uint8_t j = 0U; j < 8U; j++) - { - if (crc16 & 0x8000) - { - crc16 = (( crc16 << 1U ) ^ poly ); - } - else - { - crc16 = ( crc16 << 1U ); - } - } - } - - return crc16; - } - - //////////////////////////////////////////////////////////////////////////////// - /** - * Calculate parameter data object CRC - * - * @param[in] p_obj - Pointer to data object - * @return crc16 - Calculated CRC - */ - //////////////////////////////////////////////////////////////////////////////// - static uint8_t par_nvm_calc_obj_crc(const par_nvm_data_obj_t * const p_obj) - { - uint16_t crc = 0; - uint8_t rtn_crc = 0; - - crc = par_nvm_calc_crc((const uint8_t*) &p_obj->id, 2U ); - crc ^= par_nvm_calc_crc((const uint8_t*) &p_obj->size, 1U ); - crc ^= par_nvm_calc_crc((const uint8_t*) &p_obj->data.u8, 4U ); - rtn_crc = ( crc & 0xFFU ); - - return rtn_crc; - } - - //////////////////////////////////////////////////////////////////////////////// - /** - * Load all parameters value from NVM - * - * @param[in] num_of_par - Number of stored parameters inside NVM - * @return status - Status of operation - */ - //////////////////////////////////////////////////////////////////////////////// - static par_status_t par_nvm_load_all(const uint16_t num_of_par) - { - par_status_t status = ePAR_OK; - par_num_t par_num = 0; - uint16_t i = 0; - uint32_t obj_addr = 0; - par_nvm_data_obj_t obj_data = {0}; - nvm_status_t nvm_status = eNVM_OK; - uint8_t crc_calc = 0; - uint16_t per_par_nb = 0; - uint16_t new_par_cnt = 0; - - // Loop thru stored NVM object - for ( i = 0; i < num_of_par; i++ ) - { - // Calculate address - // NOTE: For know fixed 8 bytes! - obj_addr = (( 8 * i ) + PAR_NVM_FIRST_DATA_OBJ_ADDR ); - - // Load parameter NVM object - nvm_status = nvm_read( PAR_CFG_NVM_REGION, obj_addr, sizeof( par_nvm_data_obj_t ), (uint8_t*) &obj_data ); - - // NVM read OK - if ( eNVM_OK == nvm_status ) - { - // Calculate CRC - crc_calc = par_nvm_calc_obj_crc( &obj_data ); - - // CRC OK - if ( crc_calc == obj_data.crc ) - { - // Is that parameter in current table - if ( ePAR_OK == par_get_num_by_id( obj_data.id, &par_num )) - { - /** - * Parameter found in device and stored in NVM - * - * Check if that parameter is still persistent! - */ - if ( true == par_get_config(par_num)->persistant ) - { - // Check if already in LUT - if ( false == par_nvm_is_in_nvm_lut( obj_data.id )) - { - // Add to NVM lut - g_par_nvm_data_obj_addr[per_par_nb].id = obj_data.id; - g_par_nvm_data_obj_addr[per_par_nb].addr = obj_addr; - g_par_nvm_data_obj_addr[per_par_nb].valid = true; - - // Set parameter - par_set( par_num, &obj_data.data ); - - // Increment current persistent parameter counter - per_par_nb++; - } - } - } - - // Parameter not in current table - else - { - // No action... - } - } - - // CRC corrupted - else - { - status = ePAR_ERROR_CRC; - break; - } - } - else - { - status = ePAR_ERROR_NVM; - break; - } - } - - PAR_DBG_PRINT( "PAR_NVM: Loading all persistent parameters with status: %s", par_get_status_str(status)); - PAR_DBG_PRINT( "PAR_NVM: Nb. of stored pars in NVM: %d", num_of_par ); - PAR_DBG_PRINT( "PAR_NVM: Nb. of live persistent: \t%d", par_nvm_get_per_par()); - - // Find new persistent parameter - if ( ePAR_OK == status ) - { - for ( i = 0; i < ePAR_NUM_OF; i++ ) - { - const par_cfg_t * const par_cfg = par_get_config(par_num); - - if ( true == par_cfg->persistant ) - { - if ( false == par_nvm_is_in_nvm_lut( par_cfg->id )) - { - // Is persistant and not jet in NVM lut -> Add to LUT - g_par_nvm_data_obj_addr[per_par_nb].id = par_cfg->id; - g_par_nvm_data_obj_addr[per_par_nb].addr = obj_addr + ( 8U * ( new_par_cnt + 1U )); - g_par_nvm_data_obj_addr[per_par_nb].valid = true; - - // Write new par to NVM - par_save( i ); - - per_par_nb++; - new_par_cnt++; - } - } - } - - // If there is a new persistent parameter change HVM header - if ( new_par_cnt > 0 ) - { - // Add additional new persistent parameters number to existing one! - // NOTE: In general obj number will only rise! - status |= par_nvm_write_header( num_of_par + new_par_cnt ); - - // Sync NVM - status |= par_nvm_sync(); - - #if ( PAR_CFG_DEBUG_EN ) - PAR_DBG_PRINT( "PAR_NVM: Added %d new parameters to NVM LUT table!", new_par_cnt ); - #endif - } - } - - return status; - } - - //////////////////////////////////////////////////////////////////////////////// - /** - * Get total number of persistent parameters - * - * @return num_of_per_par - Number of persistent parameters - */ - //////////////////////////////////////////////////////////////////////////////// - static uint16_t par_nvm_get_per_par(void) - { - uint16_t num_of_per_par = 0U; - - for ( par_num_t par_num = 0; par_num < ePAR_NUM_OF; par_num++ ) - { - if ( true == par_get_config(par_num)->persistant ) - { - num_of_per_par++; - } - } - - return num_of_per_par; - } - - //////////////////////////////////////////////////////////////////////////////// - /** - * Build new parameter NVM LUT table - * - * @return void - */ - //////////////////////////////////////////////////////////////////////////////// - static void par_nvm_build_new_nvm_lut(void) - { - uint16_t per_par_nb = 0U; - - // Loop thru all parameters - for ( par_num_t par_num = 0; par_num < ePAR_NUM_OF; par_num++ ) - { - const par_cfg_t * const par_cfg = par_get_config( par_num ); - - if ( true == par_cfg->persistant ) - { - // First parameter - if ( 0 == per_par_nb ) - { - g_par_nvm_data_obj_addr[per_par_nb].addr = PAR_NVM_FIRST_DATA_OBJ_ADDR; - } - - // Build consecutive address space - // NOTE: For know each NVM data object is fixed in size (8 bytes) - else - { - // Calculate and add address of next parameter - g_par_nvm_data_obj_addr[per_par_nb].addr = ( g_par_nvm_data_obj_addr[per_par_nb-1].addr + 8U ); - } - - // Store parameter ID - g_par_nvm_data_obj_addr[per_par_nb].id = par_cfg->id; - - // Next persistent parameter - per_par_nb++; - } - } - - // Show NVM LUT table - par_nvm_print_nvm_lut(); - } - - //////////////////////////////////////////////////////////////////////////////// - /** - * Get parameter NVM object start address based on its ID - * - * @note In case ID is not found there is a problem with building the - * NVM lut!!! - * - * @param[in] id - Parameter ID - * @return obj_addr - NVM address of object with ID - */ - //////////////////////////////////////////////////////////////////////////////// - static uint32_t par_nvm_get_nvm_lut_addr(const uint16_t id) - { - for ( par_num_t par_num = 0; par_num < ePAR_NUM_OF; par_num++ ) - { - if ( id == g_par_nvm_data_obj_addr[par_num].id ) - { - return g_par_nvm_data_obj_addr[par_num].addr; - } - } - - return 0; - } - - //////////////////////////////////////////////////////////////////////////////// - /** - * Check if parameter is in NVM LUT - * - * @param[in] id - Parameter ID - * @return is_in_lut - Flag that indicated if object is in NVM lut - */ - //////////////////////////////////////////////////////////////////////////////// - static bool par_nvm_is_in_nvm_lut(const uint16_t id ) - { - for ( par_num_t par_num = 0; par_num < ePAR_NUM_OF; par_num++ ) - { - if ( ( id == g_par_nvm_data_obj_addr[par_num].id ) - && ( true == g_par_nvm_data_obj_addr[par_num].valid )) - { - return true; - } - } - - return false; - } - - //////////////////////////////////////////////////////////////////////////////// - /** - * Initialize NVM module - * - * @return status - Status of operation - */ - //////////////////////////////////////////////////////////////////////////////// - static par_status_t par_nvm_init_nvm(void) - { - par_status_t status = ePAR_OK; - bool is_nvm_init = false; - - // First check if NVM is already init - (void) nvm_is_init( &is_nvm_init ); - - // NVM is not jet init - if ( false == is_nvm_init ) - { - // Init NVM - if ( eNVM_OK != nvm_init()) - { - status = ePAR_ERROR_INIT; - PAR_DBG_PRINT( "PAR_NVM: NVM module init error!" ); - } - } - - return status; - } - - //////////////////////////////////////////////////////////////////////////////// - /** - * Sync NVM module - * - * @return status - Status of operation - */ - //////////////////////////////////////////////////////////////////////////////// - static par_status_t par_nvm_sync(void) - { - par_status_t status = ePAR_OK; - - if ( eNVM_OK != nvm_sync( PAR_CFG_NVM_REGION )) - { - status = ePAR_ERROR_NVM; - } - - return status; - } - - //////////////////////////////////////////////////////////////////////////////// - /** - * @} - */ - //////////////////////////////////////////////////////////////////////////////// - - //////////////////////////////////////////////////////////////////////////////// - /** - *@addtogroup API_PAR_NVM_FUNCTIONS - * @{ - * - * Following functions are part of Device Parameter NVM manipulation API. - */ - //////////////////////////////////////////////////////////////////////////////// - - //////////////////////////////////////////////////////////////////////////////// - /** - * Initialize parameter NVM handling - * - * @brief Based on settings in "par_cfg.h" initialisation phase is done. - * Settings such as "PAR_CFG_TABLE_ID_CHECK_EN" will affect checking - * for table ID. - * - * @return status - Status of initialisation - */ - //////////////////////////////////////////////////////////////////////////////// - par_status_t par_nvm_init(void) - { - par_status_t status = ePAR_OK; - uint16_t obj_nb = 0U; - uint16_t per_par_nb = 0U; - - // Init NVM module - status = par_nvm_init_nvm(); - - // NVM driver init OK - if ( ePAR_OK == status ) - { - // Par NVM module init - gb_is_init = true; - - // Get number of persistent parameters - per_par_nb = par_nvm_get_per_par(); - - // At least one persistent parameter - if ( per_par_nb > 0 ) - { - // Validate header - status = par_nvm_validate_header( &obj_nb ); - - // NVM header OK - if ( ePAR_OK == status ) - { - // Check hash - #if ( PAR_CFG_TABLE_ID_CHECK_EN ) - // TODO: ... - #endif - - // Load all parameters - status = par_nvm_load_all( obj_nb ); - - // Load CRC error - if ( ePAR_ERROR_CRC == status ) - { - status = par_nvm_reset_all(); - status |= ( ePAR_WAR_SET_TO_DEF | ePAR_WAR_NVM_REWRITTEN ); - } - - // NVM error - else if ( ePAR_ERROR_NVM == status ) - { - /** - * @note Set all parameters to default as it might happend - * that some of the parameters will be loaded from - * NVM and some will have default values. - * - * System might behave unexpectedly if having some - * default and some modified parameter values! - */ - par_set_all_to_default(); - status |= ePAR_WAR_SET_TO_DEF; - } - else - { - // No actions... - } - } - - // Signature NOT OK - // OR Header CRC corrupted - else if ( ( ePAR_ERROR == status ) - || ( ePAR_ERROR_CRC == status )) - { - status = par_nvm_reset_all(); - status |= ( ePAR_WAR_SET_TO_DEF | ePAR_WAR_NVM_REWRITTEN ); - } - - // NVM Error - else - { - // No actions... - } - } - - // No persistent parameters - else - { - status |= ePAR_WAR_NO_PERSISTANT; - PAR_DBG_PRINT( "PAR_NVM: No persistent parameters... Nothing to do..." ); - } - } - - return status; - } - - //////////////////////////////////////////////////////////////////////////////// - /** - * De-Initialize parameter NVM handling - * - * @return status - Status of de-init - */ - //////////////////////////////////////////////////////////////////////////////// - par_status_t par_nvm_deinit(void) - { - par_status_t status = ePAR_OK; - - if ( true == gb_is_init ) - { - if ( eNVM_OK != nvm_deinit()) - { - status = ePAR_ERROR; - } - } - else - { - status = ePAR_ERROR; - } - - return status; - } - - //////////////////////////////////////////////////////////////////////////////// - /** - * Store parameter value to NVM - * - * @note Sync has only effect when using EEPROM emulated NVM feature! When - * using Flash end memory device. - * - * @note In case of using Flash end memory for storing parameters take special - * care when enabling sync (nvm_sync=true). At each sync data from RAM - * is copied to FLASH. - * - * @param[in] par_num - Parameter enumeration number - * @param[in] nvm_sync - Perform NVM sync after parameter write - * @return status - Status of operation - */ - //////////////////////////////////////////////////////////////////////////////// - par_status_t par_nvm_write(const par_num_t par_num, const bool nvm_sync) - { - par_status_t status = ePAR_OK; - par_nvm_data_obj_t obj_data = { 0 }; - uint32_t par_addr = 0UL; - - PAR_ASSERT( true == gb_is_init ); - PAR_ASSERT( par_num < ePAR_NUM_OF ); - - if ( true == gb_is_init ) - { - if ( par_num < ePAR_NUM_OF ) - { - // Get configuration - const par_cfg_t * const par_cfg = par_get_config( par_num ); - - // Is that parameter persistent - if ( true == par_cfg->persistant ) - { - // Get current par value - par_get( par_num, (uint32_t*) &obj_data.data ); - - // Get parameter ID - obj_data.id = par_cfg->id; - - // Get parameter type size - // NOTE: For know fixed! - obj_data.size = 4U; - - // Calculate CRC - obj_data.crc = par_nvm_calc_obj_crc( &obj_data ); - - // Get address from NVM lut - par_addr = par_nvm_get_nvm_lut_addr( obj_data.id ); - - // Write to NVM - if ( eNVM_OK != nvm_write( PAR_CFG_NVM_REGION, par_addr, sizeof( par_nvm_data_obj_t ), (const uint8_t*) &obj_data )) - { - status |= ePAR_ERROR_NVM; - } - - // Sync NVM - if ( true == nvm_sync ) - { - status |= par_nvm_sync(); - } - } - else - { - status = ePAR_ERROR; - } - } - else - { - status = ePAR_ERROR; - } - } - else - { - status = ePAR_ERROR_INIT; - } - - return status; - } - - //////////////////////////////////////////////////////////////////////////////// - /** - * Store all parameter value to NVM - * - * @return status - Status of operation - */ - //////////////////////////////////////////////////////////////////////////////// - par_status_t par_nvm_write_all(void) - { - par_status_t status = ePAR_OK; - - PAR_ASSERT( true == gb_is_init ); - - if ( true == gb_is_init ) - { - // Corrupt header (enter critical) - status |= par_nvm_corrupt_signature(); - - // Got thru all parameters - for ( par_num_t par_num = 0U; par_num < ePAR_NUM_OF; par_num++ ) - { - // Store only persistant one - if ( true == par_is_persistant( par_num )) - { - // Sync will be done later - status |= par_nvm_write( par_num, false ); - } - } - - // Re-write header (exit critical) - status |= par_nvm_write_header( par_nvm_get_per_par()); - - // Sync NVM - status |= par_nvm_sync(); - - PAR_DBG_PRINT( "PAR_NVM: Storing all to NVM status: %s", par_get_status_str(status)); - } - else - { - status = ePAR_ERROR_INIT; - } - - return status; - } - - //////////////////////////////////////////////////////////////////////////////// - /** - * Reset total section of parameters NVM - * - * @brief This function completely re-write whole NVM section. - * - * It first corrupt signature in order to raise "working in progress" - * flag. - * - * @return num_of_per_par - Number of persistent parameters - */ - //////////////////////////////////////////////////////////////////////////////// - par_status_t par_nvm_reset_all(void) - { - par_status_t status = ePAR_OK; - - PAR_ASSERT( true == gb_is_init ); - - if ( true == gb_is_init ) - { - // Build new NVM lut - par_nvm_build_new_nvm_lut(); - - // Write all data object - status |= par_nvm_write_all(); - } - else - { - status = ePAR_ERROR_INIT; - } - - return status; - } - - //////////////////////////////////////////////////////////////////////////////// - /** - * Print parameter NVM table - * - * @note Only for debugging purposes - * - * @return void - */ - //////////////////////////////////////////////////////////////////////////////// - par_status_t par_nvm_print_nvm_lut(void) - { - par_status_t status = ePAR_OK; - - #if ( PAR_CFG_DEBUG_EN ) - PAR_DBG_PRINT( "PAR_NVM: Parameter NVM look-up table:" ); - PAR_DBG_PRINT( " %s\t%s\t%s\t\t%s", "#", "ID", "Addr", "Valid" ); - PAR_DBG_PRINT( "===============================" ); - - for ( par_num_t par_num = 0; par_num < ePAR_NUM_OF; par_num++ ) - { - PAR_DBG_PRINT( " %d\t%d\t0x%04X\t%d", par_num, g_par_nvm_data_obj_addr[par_num].id, - g_par_nvm_data_obj_addr[par_num].addr, - g_par_nvm_data_obj_addr[par_num].valid ); - PAR_DBG_PRINT( "-----------------------------" ); - } - #endif - - return status; - } - - //////////////////////////////////////////////////////////////////////////////// - /** - * @} - */ - //////////////////////////////////////////////////////////////////////////////// - -#endif // 1 == PAR_CFG_NVM_EN diff --git a/src/par_nvm.h b/src/par_nvm.h deleted file mode 100644 index 3282912..0000000 --- a/src/par_nvm.h +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright (c) 2026 Ziga Miklosic -// All Rights Reserved -// This software is under MIT licence (https://opensource.org/licenses/MIT) -//////////////////////////////////////////////////////////////////////////////// -/** -*@file par_nvm.h -*@brief Parameter storage to non-volatile memory -*@author Ziga Miklosic -*@email ziga.miklosic@gmail.com -*@date 29.01.2026 -*@version V3.0.1 -*/ -//////////////////////////////////////////////////////////////////////////////// -/** -*@addtogroup PAR_NVM -* @{ -*/ -//////////////////////////////////////////////////////////////////////////////// - -#ifndef _PAR_NVM_H_ -#define _PAR_NVM_H_ - -//////////////////////////////////////////////////////////////////////////////// -// Includes -//////////////////////////////////////////////////////////////////////////////// -#include -#include -#include - -#include "par.h" - -#if ( 1 == PAR_CFG_NVM_EN ) - - //////////////////////////////////////////////////////////////////////////////// - // Functions Prototypes - //////////////////////////////////////////////////////////////////////////////// - par_status_t par_nvm_init (void); - par_status_t par_nvm_deinit (void); - par_status_t par_nvm_write (const par_num_t par_num, const bool nvm_sync); - par_status_t par_nvm_write_all (void); - par_status_t par_nvm_reset_all (void); - par_status_t par_nvm_print_nvm_lut (void); - -#endif // 1 == PAR_CFG_NVM_EN - -//////////////////////////////////////////////////////////////////////////////// -/** -* @} -*/ -//////////////////////////////////////////////////////////////////////////////// - -#endif // _PAR_NVM_H_ diff --git a/src/persist/backend/par_store_backend.h b/src/persist/backend/par_store_backend.h new file mode 100644 index 0000000..a2846da --- /dev/null +++ b/src/persist/backend/par_store_backend.h @@ -0,0 +1,147 @@ +/** + * @file par_store_backend.h + * @brief Declare the abstract parameter-storage backend interface. + * @author wdfk-prog + * @version 1.0 + * @date 2026-03-29 + * + * @copyright Copyright (c) 2026 Ziga Miklosic. Distributed under the MIT license. + * + * @details par_nvm.c uses this interface instead of depending directly on a + * concrete NVM repository layout. Integrators may provide any storage backend + * that supports the required byte-addressable operations. + * @note : + * @par Change Log: + * Date Version Author Description + * 2026-03-29 1.0 wdfk-prog first version + */ +#ifndef _PAR_STORE_BACKEND_H_ +#define _PAR_STORE_BACKEND_H_ + +#include +#include + +#include "par.h" + +#if (1 == PAR_CFG_NVM_EN) +/** + * @brief Abstract parameter-storage backend contract. + * + * @details All offsets are relative to the storage region reserved for the + * parameter image. The backend owns any region, partition, or device-specific + * context needed to execute the operation. + * + * If the selected storage medium provides ECC or other read-health reporting, + * policy handling is intentionally left above this abstraction. The backend + * may log or expose such information through its own implementation-specific + * means, but the parameter core does not mandate whether the application must + * rebuild, reset, continue running, or only report the event. That recovery + * decision belongs to the business layer because acceptable behavior is + * product-specific. + */ +typedef struct +{ + /** + * @brief Initialize the storage backend. + * + * @details Prepare the reserved storage region for later byte-addressable + * access. This may mount a partition, initialize a driver, or verify the + * backend context required by the concrete implementation. + * + * @return ePAR_OK on success, otherwise an implementation-defined error. + */ + par_status_t (*init)(void); + /** + * @brief Deinitialize the storage backend. + * + * @details Release resources acquired by @ref init when the parameter + * module owns backend initialization. + * + * @return ePAR_OK on success, otherwise an implementation-defined error. + */ + par_status_t (*deinit)(void); + /** + * @brief Query whether the storage backend is initialized. + * + * @param[out] p_is_init Receives the backend initialization state. + * Must not be NULL. + * + * @return ePAR_OK when the state was reported successfully, otherwise an + * implementation-defined error. + */ + par_status_t (*is_init)(bool * const p_is_init); + /** + * @brief Read raw bytes from the storage backend. + * + * @param[in] addr Byte offset inside the reserved parameter-storage region. + * @param[in] size Number of bytes to read. + * @param[out] p_buf Destination buffer that receives @p size bytes. + * Must not be NULL. + * + * @return ePAR_OK on success, otherwise an implementation-defined error. + */ + par_status_t (*read)(const uint32_t addr, const uint32_t size, uint8_t * const p_buf); + /** + * @brief Write raw bytes to the storage backend. + * + * @param[in] addr Byte offset inside the reserved parameter-storage region. + * @param[in] size Number of bytes to write. + * @param[in] p_buf Source buffer that provides @p size bytes. + * Must not be NULL. + * + * @return ePAR_OK on success, otherwise an implementation-defined error. + */ + par_status_t (*write)(const uint32_t addr, const uint32_t size, const uint8_t * const p_buf); + /** + * @brief Erase raw bytes in the storage backend. + * + * @param[in] addr Byte offset inside the reserved parameter-storage region. + * @param[in] size Number of bytes to erase. + * + * @return ePAR_OK on success, otherwise an implementation-defined error. + */ + par_status_t (*erase)(const uint32_t addr, const uint32_t size); + /** + * @brief Flush pending backend data to the final storage medium. + * + * @details Call this after writes or erases when the backend may stage data + * in RAM, caches, controller FIFOs, or deferred commit queues. + * + * @return ePAR_OK on success, otherwise an implementation-defined error. + */ + par_status_t (*sync)(void); + /** + * @brief Optional backend name for diagnostics. + * + * @details Set to NULL when no human-readable backend name is available. + */ + const char *name; +} par_store_backend_api_t; + +/** + * @brief Bind or prepare the active parameter-storage backend. + * + * @details Call this before @ref par_store_backend_get_api when the selected + * backend needs to attach one concrete storage port, partition, or device + * context. Pure backends that do not need a pre-bind step should return + * @ref ePAR_OK without side effects. + * + * @return ePAR_OK on success, otherwise an implementation-defined error. + */ +par_status_t par_store_backend_bind(void); + +/** + * @brief Resolve the active parameter-storage backend API. + * + * @details Link exactly one concrete implementation when `PAR_CFG_NVM_EN = 1`. + * The package can provide the GeneralEmbeddedCLibraries/nvm adapter, or the + * application can provide its own implementation. This accessor must be side + * effect free; any backend-specific binding work belongs in + * @ref par_store_backend_bind. + * + * @return Pointer to backend API, or NULL if no backend is available. + */ +const par_store_backend_api_t * par_store_backend_get_api(void); +#endif /* 1 == PAR_CFG_NVM_EN */ + +#endif /* _PAR_STORE_BACKEND_H_ */ diff --git a/src/persist/backend/par_store_backend_flash_ee.c b/src/persist/backend/par_store_backend_flash_ee.c new file mode 100644 index 0000000..1a208a7 --- /dev/null +++ b/src/persist/backend/par_store_backend_flash_ee.c @@ -0,0 +1,1940 @@ +/** + * @file par_store_backend_flash_ee.c + * @brief Implement the generic flash-emulated EEPROM backend core. + * @author wdfk-prog + * @version 1.0 + * @date 2026-04-14 + * + * @copyright Copyright (c) 2026 Ziga Miklosic. Distributed under the MIT license. + * + * @details The backend exposes a byte-addressable persistence interface to + * @ref par_nvm.c, while storing data on erase-before-write flash media. + * + * The core keeps a bounded write-back cache and appends fixed-size records to + * the active flash bank during @ref sync. The flash region is split into two + * banks. When the active bank becomes full, the backend performs a streaming + * checkpoint into the inactive bank and then switches banks atomically through + * a header state transition. The generic core does not depend on RT-Thread or + * any flash SDK. Platform adapters provide the physical flash access through + * @ref par_store_flash_ee_port_api_t. + * + * @note : + * @par Change Log: + * Date Version Author Description + * 2026-04-14 1.0 wdfk-prog first version + */ +#include "persist/backend/par_store_backend_flash_ee.h" + +#if (1 == PAR_CFG_NVM_EN) && (1 == PAR_CFG_NVM_BACKEND_FLASH_EE_EN) + +#include +#include +#include "port/par_if.h" + + +#define PAR_STORE_FLASH_EE_BANK_COUNT (2u) +#define PAR_STORE_FLASH_EE_HEADER_MAGIC (0x50454548u) /* PEEH */ +#define PAR_STORE_FLASH_EE_RECORD_MAGIC (0x50454552u) /* PEER */ +#define PAR_STORE_FLASH_EE_HEADER_PREPARE (0xFFFFFF00u) +#define PAR_STORE_FLASH_EE_HEADER_ACTIVE (0xFFFF0000u) +#define PAR_STORE_FLASH_EE_ERASE_VALUE (0xFFu) +#define PAR_STORE_FLASH_EE_HEADER_SIZE (64u) +#define PAR_STORE_FLASH_EE_RECORD_META_SIZE (12u) +#define PAR_STORE_FLASH_EE_RECORD_BUF_SIZE (PAR_CFG_NVM_BACKEND_FLASH_EE_LINE_SIZE + PAR_STORE_FLASH_EE_RECORD_META_SIZE + (2u * PAR_CFG_NVM_BACKEND_FLASH_EE_PROGRAM_SIZE)) +#define PAR_STORE_FLASH_EE_STATE_PATCH_BUF_SIZE \ + (((2u * PAR_CFG_NVM_BACKEND_FLASH_EE_PROGRAM_SIZE) >= 4u) ? (2u * PAR_CFG_NVM_BACKEND_FLASH_EE_PROGRAM_SIZE) : 4u) +#define PAR_STORE_FLASH_EE_CACHE_LINE_COUNT (PAR_CFG_NVM_BACKEND_FLASH_EE_CACHE_SIZE / PAR_CFG_NVM_BACKEND_FLASH_EE_LINE_SIZE) +#define PAR_STORE_FLASH_EE_DIRTY_BYTES ((PAR_STORE_FLASH_EE_CACHE_LINE_COUNT + 7u) / 8u) + +/** + * @brief Describe one persistent flash-bank header. + */ +typedef struct +{ + uint32_t magic; /**< Header magic used to identify a valid bank. */ + uint32_t version; /**< On-flash backend format version. */ + uint32_t logical_size; /**< Exposed logical EEPROM size in bytes. */ + uint32_t line_size; /**< Fixed payload size carried by each append record. */ + uint32_t record_size; /**< Total append-record size including payload, metadata, padding, and commit unit. */ + uint32_t bank_size; /**< Physical size of one bank inside the persistence region. */ + uint32_t seq; /**< Monotonic bank sequence used to pick the newest bank. */ + uint32_t cfg_crc; /**< CRC of the geometry fields stored in the header. */ + uint32_t state; /**< Header state word, for example prepare or active. */ + uint32_t reserved[7]; /**< Reserved words kept erased for later format extension. */ +} par_store_flash_ee_bank_header_t; + +/** + * @brief Describe the metadata stored before the final commit unit. + * + * @details Each record stores the payload first, then this metadata block, + * optional erased padding, and finally one dedicated commit unit. The record + * becomes visible only after the commit unit is programmed successfully. + */ +typedef struct +{ + uint32_t line_index; /**< Logical line index updated by this record. */ + uint16_t payload_size; /**< Payload size in bytes. Always equals line_size. */ + uint16_t record_crc; /**< CRC of the payload bytes plus semantic metadata fields. */ + uint32_t reserved; /**< Reserved metadata word for future extension. */ +} par_store_flash_ee_record_meta_t; + +/** + * @brief Hold the live runtime state of the backend core. + * + * @details The context tracks the selected active bank, the current append + * tail, and the location of the in-RAM cache window. The cache window is only + * a bounded staging area; committed data remains in flash append records. + */ +typedef struct +{ + bool is_bound; /**< True once one physical port is bound. */ + bool is_init; /**< True once the backend core finished initialization. */ + bool cache_valid; /**< True once the cache window contains reconstructed data. */ + const par_store_flash_ee_port_api_t *p_port_api; /**< Bound physical port API table. */ + void *p_port_ctx; /**< Opaque physical port context. */ + uint32_t region_size; /**< Full persistence-region size in bytes. */ + uint32_t bank_size; /**< Size of one physical bank in bytes. */ + uint32_t erase_size; /**< Physical erase granularity in bytes. */ + uint32_t program_size; /**< Physical program granularity in bytes. */ + uint32_t logical_size; /**< Exposed logical EEPROM size in bytes. */ + uint32_t line_size; /**< Fixed logical line size in bytes. */ + uint32_t line_count; /**< Number of logical lines in the address space. */ + uint32_t record_size; /**< Total append-record size in bytes. */ + uint32_t active_bank_base; /**< Base address of the currently active bank. */ + uint32_t inactive_bank_base; /**< Base address of the checkpoint target bank. */ + uint32_t active_seq; /**< Sequence number of the active bank. */ + uint32_t active_next_offset; /**< Next free append offset inside the active bank. */ + uint32_t cache_base; /**< Logical base address covered by the cache window. */ + uint32_t cache_size; /**< Actual cache-window size in bytes. */ + par_store_flash_ee_diag_t diag; /**< Latest diagnostic code. */ +} par_store_flash_ee_ctx_t; + +/** + * @brief Hold the single backend instance state. + */ +static par_store_flash_ee_ctx_t g_par_store_flash_ee_ctx = { + .is_bound = false, + .is_init = false, + .cache_valid = false, + .p_port_api = NULL, + .p_port_ctx = NULL, + .region_size = 0u, + .bank_size = 0u, + .erase_size = 0u, + .program_size = 0u, + .logical_size = 0u, + .line_size = PAR_CFG_NVM_BACKEND_FLASH_EE_LINE_SIZE, + .line_count = 0u, + .record_size = 0u, + .active_bank_base = 0u, + .inactive_bank_base = 0u, + .active_seq = 0u, + .active_next_offset = 0u, + .cache_base = 0u, + .cache_size = 0u, + .diag = ePAR_STORE_FLASH_EE_DIAG_NONE, +}; + +/** + * @brief Store the bounded RAM cache window. + * + * @details The cache does not mirror the full logical EEPROM space. It only + * keeps one aligned window so write, erase, and read operations can work on a + * small hot region without allocating logical_size bytes of RAM. + */ +static uint8_t g_par_store_flash_ee_cache[PAR_CFG_NVM_BACKEND_FLASH_EE_CACHE_SIZE]; + +/** + * @brief Store one dirty bit per cached line. + * + * @details A dirty bit marks that the corresponding cache line differs from + * the committed flash view and still needs to be appended during sync. + */ +static uint8_t g_par_store_flash_ee_dirty[PAR_STORE_FLASH_EE_DIRTY_BYTES]; + +/** + * @brief Flush staged cache data into physical flash. + * + * @return ePAR_OK on success, otherwise an error code. + */ +static par_status_t par_store_flash_ee_sync(void); + +/** + * @brief Clear all cache dirty bits. + */ +static void par_store_flash_ee_clear_dirty(void); + +/** + * @brief Return true when one memory block is fully erased. + * + * @param[in] p_buf Buffer to inspect. + * @param[in] size Buffer length in bytes. + * @return true when the block contains only erased bytes; otherwise false. + */ +static bool par_store_flash_ee_is_erased_block(const uint8_t *p_buf, uint32_t size) +{ + uint32_t idx = 0u; + + for (idx = 0u; idx < size; ++idx) + { + if (PAR_STORE_FLASH_EE_ERASE_VALUE != p_buf[idx]) + { + return false; + } + } + + return true; +} + +/** + * @brief Update the backend diagnostic code. + * + * @param[in] diag New diagnostic code. + */ +static void par_store_flash_ee_set_diag(par_store_flash_ee_diag_t diag) +{ + g_par_store_flash_ee_ctx.diag = diag; +} + +/** + * @brief Normalize a top-level backend return status. + * + * @details Successful top-level operations clear the last diagnostic so + * @ref par_store_backend_flash_ee_get_diag reports the latest backend event + * instead of keeping an older transient warning or failure forever. + * + * @param[in] status Operation result to normalize. + * @return The original status value. + */ +static par_status_t par_store_flash_ee_complete_status(par_status_t status) +{ + if (ePAR_OK == status) + { + par_store_flash_ee_set_diag(ePAR_STORE_FLASH_EE_DIAG_NONE); + } + + return status; +} + +/** + * @brief Reset transient runtime state after a failed or completed session. + * + * @details Binding information stays intact so the same adapter can try + * initialization again, but all derived geometry, bank-selection, and cache + * state is cleared to avoid exposing a half-initialized backend view. + */ +static void par_store_flash_ee_reset_runtime_state(void) +{ + g_par_store_flash_ee_ctx.is_init = false; + g_par_store_flash_ee_ctx.cache_valid = false; + g_par_store_flash_ee_ctx.region_size = 0u; + g_par_store_flash_ee_ctx.bank_size = 0u; + g_par_store_flash_ee_ctx.erase_size = 0u; + g_par_store_flash_ee_ctx.program_size = 0u; + g_par_store_flash_ee_ctx.logical_size = 0u; + g_par_store_flash_ee_ctx.line_size = PAR_CFG_NVM_BACKEND_FLASH_EE_LINE_SIZE; + g_par_store_flash_ee_ctx.line_count = 0u; + g_par_store_flash_ee_ctx.record_size = 0u; + g_par_store_flash_ee_ctx.active_bank_base = 0u; + g_par_store_flash_ee_ctx.inactive_bank_base = 0u; + g_par_store_flash_ee_ctx.active_seq = 0u; + g_par_store_flash_ee_ctx.active_next_offset = 0u; + g_par_store_flash_ee_ctx.cache_base = 0u; + g_par_store_flash_ee_ctx.cache_size = 0u; + par_store_flash_ee_clear_dirty(); +} + +/** + * @brief Best-effort cleanup after backend init fails after the port opened. + * + * @details Once the physical port init succeeded, later failures must unwind + * the opened port and clear transient runtime state so the next init attempt + * does not inherit a partially initialized backend context. + */ +static void par_store_flash_ee_cleanup_failed_init(void) +{ + if ((NULL != g_par_store_flash_ee_ctx.p_port_api) && + (NULL != g_par_store_flash_ee_ctx.p_port_api->deinit)) + { + const par_status_t cleanup_status = + g_par_store_flash_ee_ctx.p_port_api->deinit(g_par_store_flash_ee_ctx.p_port_ctx); + + if (ePAR_OK != cleanup_status) + { + PAR_ERR_PRINT("PAR_FLASH_EE: port deinit after failed init failed, err=%u", + (unsigned)cleanup_status); + } + } + + par_store_flash_ee_reset_runtime_state(); +} + +/** + * @brief Return true when a failed bank scan may be recovered by reformatting. + * + * @details Scan failures caused by structural incompatibility or corrupted + * on-flash contents may be recovered by rebuilding the banks. Transport-level + * flash read failures must not be treated as safe-to-format because they do + * not prove that both banks are actually empty or unusable. + * + * @param[in] diag Diagnostic captured from the failed scan. + * @return true when automatic reformat is allowed; otherwise false. + */ +static bool par_store_flash_ee_scan_diag_allows_reformat(par_store_flash_ee_diag_t diag) +{ + switch (diag) + { + case ePAR_STORE_FLASH_EE_DIAG_BANK_ERASED: + case ePAR_STORE_FLASH_EE_DIAG_HEADER_MAGIC: + case ePAR_STORE_FLASH_EE_DIAG_HEADER_VERSION: + case ePAR_STORE_FLASH_EE_DIAG_HEADER_STATE: + case ePAR_STORE_FLASH_EE_DIAG_HEADER_CRC: + case ePAR_STORE_FLASH_EE_DIAG_HEADER_LAYOUT: + case ePAR_STORE_FLASH_EE_DIAG_RECORD_MAGIC: + case ePAR_STORE_FLASH_EE_DIAG_RECORD_RANGE: + case ePAR_STORE_FLASH_EE_DIAG_RECORD_CRC: + return true; + + default: + return false; + } +} + +/** + * @brief Read raw bytes from the bound physical flash port. + * + * @param[in] addr Byte offset inside the persistence region. + * @param[in] size Number of bytes to read. + * @param[out] p_buf Destination buffer. + * @return ePAR_OK on success, otherwise @ref ePAR_ERROR_NVM. + */ +static par_status_t par_store_flash_ee_port_read(uint32_t addr, uint32_t size, uint8_t *p_buf) +{ + par_status_t status = ePAR_ERROR_NVM; + + status = g_par_store_flash_ee_ctx.p_port_api->read(g_par_store_flash_ee_ctx.p_port_ctx, addr, size, p_buf); + if (ePAR_OK != status) + { + par_store_flash_ee_set_diag(ePAR_STORE_FLASH_EE_DIAG_PORT_READ); + PAR_ERR_PRINT("PAR_FLASH_EE: port read failed, addr=%lu, size=%lu", (unsigned long)addr, (unsigned long)size); + } + + return status; +} + +/** + * @brief Program raw bytes through the bound physical flash port. + * + * @param[in] addr Byte offset inside the persistence region. + * @param[in] size Number of bytes to program. + * @param[in] p_buf Source buffer. + * @return ePAR_OK on success, otherwise @ref ePAR_ERROR_NVM. + */ +static par_status_t par_store_flash_ee_port_program(uint32_t addr, uint32_t size, const uint8_t *p_buf) +{ + par_status_t status = ePAR_ERROR_NVM; + + status = g_par_store_flash_ee_ctx.p_port_api->program(g_par_store_flash_ee_ctx.p_port_ctx, addr, size, p_buf); + if (ePAR_OK != status) + { + par_store_flash_ee_set_diag(ePAR_STORE_FLASH_EE_DIAG_PORT_PROGRAM); + PAR_ERR_PRINT("PAR_FLASH_EE: port program failed, addr=%lu, size=%lu", (unsigned long)addr, (unsigned long)size); + } + + return status; +} + +/** + * @brief Erase raw bytes through the bound physical flash port. + * + * @param[in] addr Byte offset inside the persistence region. + * @param[in] size Number of bytes to erase. + * @return ePAR_OK on success, otherwise @ref ePAR_ERROR_NVM. + */ +static par_status_t par_store_flash_ee_port_erase(uint32_t addr, uint32_t size) +{ + par_status_t status = ePAR_ERROR_NVM; + + status = g_par_store_flash_ee_ctx.p_port_api->erase(g_par_store_flash_ee_ctx.p_port_ctx, addr, size); + if (ePAR_OK != status) + { + par_store_flash_ee_set_diag(ePAR_STORE_FLASH_EE_DIAG_PORT_ERASE); + PAR_ERR_PRINT("PAR_FLASH_EE: port erase failed, addr=%lu, size=%lu", (unsigned long)addr, (unsigned long)size); + } + + return status; +} + +/** + * @brief Calculate the persistent bank-header CRC. + * + * @param[in] p_header Header to hash. + * @return CRC16 value widened to 32 bits. + */ +static uint32_t par_store_flash_ee_calc_header_crc(const par_store_flash_ee_bank_header_t *p_header) +{ + uint16_t crc = PAR_IF_CRC16_INIT; + uint32_t size = (uint32_t)offsetof(par_store_flash_ee_bank_header_t, cfg_crc); + + crc = par_if_crc16_accumulate(crc, (const uint8_t *)p_header, size); + return (uint32_t)crc; +} + +/** + * @brief Calculate one append-record integrity CRC. + * + * @details The CRC covers the payload bytes and the metadata fields that define + * the record semantics during reconstruction. This prevents silently accepting + * a committed payload under a corrupted logical line index or payload size. + * + * @param[in] p_payload Payload bytes. + * @param[in] payload_size Payload length in bytes. + * @param[in] line_index Logical line index stored in the record metadata. + * @return CRC16 value. + */ +static uint16_t par_store_flash_ee_calc_record_crc(const uint8_t *p_payload, uint16_t payload_size, uint32_t line_index) +{ + uint16_t crc = PAR_IF_CRC16_INIT; + + crc = par_if_crc16_accumulate(crc, p_payload, (uint32_t)payload_size); + crc = par_if_crc16_accumulate(crc, (const uint8_t *)&line_index, sizeof(line_index)); + crc = par_if_crc16_accumulate(crc, (const uint8_t *)&payload_size, sizeof(payload_size)); + return crc; +} + +/** + * @brief Return one bank base offset. + * + * @param[in] bank_index Bank index, either 0 or 1. + * @return Bank base offset in bytes. + */ +static uint32_t par_store_flash_ee_bank_base(uint32_t bank_index) +{ + return (bank_index * g_par_store_flash_ee_ctx.bank_size); +} + +/** + * @brief Test whether one cache line is marked dirty. + * + * @param[in] cache_line Cache-line index inside the current cache window. + * @return true when dirty; otherwise false. + */ +static bool par_store_flash_ee_is_dirty(uint32_t cache_line) +{ + return (0u != (g_par_store_flash_ee_dirty[cache_line / 8u] & (uint8_t)(1u << (cache_line % 8u)))); +} + +/** + * @brief Mark one cache line as dirty. + * + * @param[in] cache_line Cache-line index inside the current cache window. + */ +static void par_store_flash_ee_mark_dirty(uint32_t cache_line) +{ + g_par_store_flash_ee_dirty[cache_line / 8u] |= (uint8_t)(1u << (cache_line % 8u)); +} + +/** + * @brief Clear all cache dirty bits. + */ +static void par_store_flash_ee_clear_dirty(void) +{ + (void)memset(g_par_store_flash_ee_dirty, 0, sizeof(g_par_store_flash_ee_dirty)); +} + +/** + * @brief Return true when the current cache contains dirty lines. + * + * @return true when dirty data is pending; otherwise false. + */ +static bool par_store_flash_ee_has_dirty(void) +{ + uint32_t idx = 0u; + + for (idx = 0u; idx < (uint32_t)sizeof(g_par_store_flash_ee_dirty); ++idx) + { + if (0u != g_par_store_flash_ee_dirty[idx]) + { + return true; + } + } + + return false; +} + + +/** + * @brief Count dirty cache lines inside the active cache window. + * + * @details The returned value is later converted into the number of append + * records needed during sync. Each dirty cache line becomes exactly one record + * append unless a checkpoint is triggered first. + * + * @return Number of dirty cache lines. + */ +static uint32_t par_store_flash_ee_count_dirty(void) +{ + uint32_t count = 0u; + uint32_t cache_line = 0u; + + for (cache_line = 0u; cache_line < (g_par_store_flash_ee_ctx.cache_size / g_par_store_flash_ee_ctx.line_size); ++cache_line) + { + if (true == par_store_flash_ee_is_dirty(cache_line)) + { + ++count; + } + } + + return count; +} + +/** + * @brief Align one value upward to the requested granularity. + * + * @param[in] value Input value to align. + * @param[in] align Alignment granularity in bytes. + * @return Aligned value. + */ +static uint32_t par_store_flash_ee_align_up(uint32_t value, uint32_t align) +{ + return ((value + align - 1u) / align) * align; +} + +/** + * @brief Return the metadata offset inside one append record. + * + * @return Metadata offset in bytes. + */ +static uint32_t par_store_flash_ee_record_meta_offset(void) +{ + return g_par_store_flash_ee_ctx.line_size; +} + +/** + * @brief Return the commit-unit offset inside one append record. + * + * @return Commit-unit offset in bytes. + */ +static uint32_t par_store_flash_ee_record_commit_offset(void) +{ + return par_store_flash_ee_align_up(par_store_flash_ee_record_meta_offset() + PAR_STORE_FLASH_EE_RECORD_META_SIZE, + g_par_store_flash_ee_ctx.program_size); +} + +/** + * @brief Build the expected commit-unit bytes. + * + * @param[out] p_commit_buf Destination buffer with program-size bytes. + */ +static void par_store_flash_ee_build_commit_unit(uint8_t *p_commit_buf) +{ + uint32_t magic = PAR_STORE_FLASH_EE_RECORD_MAGIC; + uint8_t magic_bytes[sizeof(uint32_t)]; + uint32_t idx = 0u; + + (void)memcpy(magic_bytes, &magic, sizeof(magic_bytes)); + for (idx = 0u; idx < g_par_store_flash_ee_ctx.program_size; ++idx) + { + p_commit_buf[idx] = magic_bytes[idx % (uint32_t)sizeof(magic_bytes)]; + } +} + +/** + * @brief Return true when one commit unit matches the committed pattern. + * + * @param[in] p_commit_buf Commit-unit bytes read from flash. + * @return true when the record is committed; otherwise false. + */ +static bool par_store_flash_ee_is_valid_commit_unit(const uint8_t *p_commit_buf) +{ + uint8_t expected_commit[PAR_CFG_NVM_BACKEND_FLASH_EE_PROGRAM_SIZE]; + + if (NULL == p_commit_buf) + { + return false; + } + + par_store_flash_ee_build_commit_unit(expected_commit); + return (0 == memcmp(p_commit_buf, expected_commit, g_par_store_flash_ee_ctx.program_size)); +} + +/** + * @brief Return true when one commit unit still contains erased bytes. + * + * @param[in] p_commit_buf Commit-unit bytes read from flash. + * @return true when the commit unit is still open; otherwise false. + */ +static bool par_store_flash_ee_commit_unit_has_erased_byte(const uint8_t *p_commit_buf) +{ + uint32_t idx = 0u; + + if (NULL == p_commit_buf) + { + return false; + } + + for (idx = 0u; idx < g_par_store_flash_ee_ctx.program_size; ++idx) + { + if (PAR_STORE_FLASH_EE_ERASE_VALUE == p_commit_buf[idx]) + { + return true; + } + } + + return false; +} + +/** + * @brief Parse one on-flash record metadata block. + * + * @param[in] p_record_buf Full record bytes. + * @param[out] p_meta Receives the parsed metadata. + */ +static void par_store_flash_ee_parse_record_meta(const uint8_t *p_record_buf, par_store_flash_ee_record_meta_t *p_meta) +{ + (void)memcpy(p_meta, + &p_record_buf[par_store_flash_ee_record_meta_offset()], + sizeof(*p_meta)); +} + +/** + * @brief Validate static and runtime backend geometry. + * + * @return ePAR_OK on success, otherwise an error code. + */ +static par_status_t par_store_flash_ee_validate_geometry(void) +{ + uint32_t max_records = 0u; + + if ((0u == g_par_store_flash_ee_ctx.region_size) || + (0u == g_par_store_flash_ee_ctx.erase_size) || + (0u == g_par_store_flash_ee_ctx.program_size)) + { + par_store_flash_ee_set_diag(ePAR_STORE_FLASH_EE_DIAG_PORT_GEOMETRY); + return ePAR_ERROR_INIT; + } + + if (g_par_store_flash_ee_ctx.program_size > PAR_CFG_NVM_BACKEND_FLASH_EE_PROGRAM_SIZE) + { + par_store_flash_ee_set_diag(ePAR_STORE_FLASH_EE_DIAG_CONFIG); + return ePAR_ERROR_INIT; + } + + if ((0u == PAR_CFG_NVM_BACKEND_FLASH_EE_CACHE_SIZE) || + (0u == PAR_CFG_NVM_BACKEND_FLASH_EE_LINE_SIZE) || + (0u == PAR_CFG_NVM_BACKEND_FLASH_EE_LOGICAL_SIZE) || + (0u != (PAR_CFG_NVM_BACKEND_FLASH_EE_CACHE_SIZE % PAR_CFG_NVM_BACKEND_FLASH_EE_LINE_SIZE)) || + (0u != (PAR_CFG_NVM_BACKEND_FLASH_EE_LOGICAL_SIZE % PAR_CFG_NVM_BACKEND_FLASH_EE_LINE_SIZE))) + { + par_store_flash_ee_set_diag(ePAR_STORE_FLASH_EE_DIAG_CONFIG); + return ePAR_ERROR_INIT; + } + + g_par_store_flash_ee_ctx.bank_size = g_par_store_flash_ee_ctx.region_size / PAR_STORE_FLASH_EE_BANK_COUNT; + g_par_store_flash_ee_ctx.logical_size = PAR_CFG_NVM_BACKEND_FLASH_EE_LOGICAL_SIZE; + g_par_store_flash_ee_ctx.line_size = PAR_CFG_NVM_BACKEND_FLASH_EE_LINE_SIZE; + g_par_store_flash_ee_ctx.line_count = (g_par_store_flash_ee_ctx.logical_size + g_par_store_flash_ee_ctx.line_size - 1u) / + g_par_store_flash_ee_ctx.line_size; + g_par_store_flash_ee_ctx.record_size = par_store_flash_ee_record_commit_offset() + g_par_store_flash_ee_ctx.program_size; + + if ((0u != (PAR_STORE_FLASH_EE_HEADER_SIZE % g_par_store_flash_ee_ctx.program_size)) || + (0u != (g_par_store_flash_ee_ctx.record_size % g_par_store_flash_ee_ctx.program_size))) + { + par_store_flash_ee_set_diag(ePAR_STORE_FLASH_EE_DIAG_CONFIG); + return ePAR_ERROR_INIT; + } + + if ((0u != (g_par_store_flash_ee_ctx.bank_size % g_par_store_flash_ee_ctx.erase_size)) || + (g_par_store_flash_ee_ctx.bank_size <= PAR_STORE_FLASH_EE_HEADER_SIZE)) + { + par_store_flash_ee_set_diag(ePAR_STORE_FLASH_EE_DIAG_PORT_GEOMETRY); + return ePAR_ERROR_INIT; + } + + max_records = (g_par_store_flash_ee_ctx.bank_size - PAR_STORE_FLASH_EE_HEADER_SIZE) / + g_par_store_flash_ee_ctx.record_size; + if (max_records <= g_par_store_flash_ee_ctx.line_count) + { + par_store_flash_ee_set_diag(ePAR_STORE_FLASH_EE_DIAG_CAPACITY); + PAR_ERR_PRINT("PAR_FLASH_EE: bank capacity too small, max_records=%lu, line_count=%lu (need one extra append slot)", + (unsigned long)max_records, + (unsigned long)g_par_store_flash_ee_ctx.line_count); + return ePAR_ERROR_INIT; + } + + return ePAR_OK; +} + +/** + * @brief Probe one bank header and report its compatibility result. + + * + * @param[in] p_header Bank header bytes. + * @return Diagnostic code describing the probe result. + */ +static par_store_flash_ee_diag_t par_store_flash_ee_probe_header(const par_store_flash_ee_bank_header_t *p_header) +{ + if (true == par_store_flash_ee_is_erased_block((const uint8_t *)p_header, sizeof(*p_header))) + { + return ePAR_STORE_FLASH_EE_DIAG_BANK_ERASED; + } + + if (PAR_STORE_FLASH_EE_HEADER_MAGIC != p_header->magic) + { + return ePAR_STORE_FLASH_EE_DIAG_HEADER_MAGIC; + } + + if (PAR_CFG_NVM_BACKEND_FLASH_EE_VERSION != p_header->version) + { + return ePAR_STORE_FLASH_EE_DIAG_HEADER_VERSION; + } + + if (PAR_STORE_FLASH_EE_HEADER_ACTIVE != p_header->state) + { + return ePAR_STORE_FLASH_EE_DIAG_HEADER_STATE; + } + + if (par_store_flash_ee_calc_header_crc(p_header) != p_header->cfg_crc) + { + return ePAR_STORE_FLASH_EE_DIAG_HEADER_CRC; + } + + if ((g_par_store_flash_ee_ctx.logical_size != p_header->logical_size) || + (g_par_store_flash_ee_ctx.line_size != p_header->line_size) || + (g_par_store_flash_ee_ctx.record_size != p_header->record_size) || + (g_par_store_flash_ee_ctx.bank_size != p_header->bank_size)) + { + return ePAR_STORE_FLASH_EE_DIAG_HEADER_LAYOUT; + } + + return ePAR_STORE_FLASH_EE_DIAG_NONE; +} + +/** + * @brief Return true when one programmed slot is still uncommitted. + * + * @details The flash-ee protocol writes payload and metadata first, then + * programs one dedicated commit unit last. A slot is therefore treated as + * uncommitted when it is not fully erased and its commit unit still contains at + * least one erased byte. + * + * @param[in] p_record_buf Full record-sized buffer read from flash. + * @return true when the slot is uncommitted; otherwise false. + */ +static bool par_store_flash_ee_is_uncommitted_record_slot(const uint8_t *p_record_buf) +{ + const uint32_t commit_offset = par_store_flash_ee_record_commit_offset(); + + if (NULL == p_record_buf) + { + return false; + } + + if (true == par_store_flash_ee_is_erased_block(p_record_buf, g_par_store_flash_ee_ctx.record_size)) + { + return false; + } + + return par_store_flash_ee_commit_unit_has_erased_byte(&p_record_buf[commit_offset]); +} + +/** + * @brief Scan one active bank and return the first free record offset. + * + * @param[in] bank_base Bank base offset. + * @param[out] p_seq Receives the header sequence number. + * @param[out] p_next_offset Receives the next append offset inside the bank. + * @return ePAR_OK when the bank is valid enough to use; otherwise an error. + * + * @note Scan accepts one fully erased record slot as the end of the append + * region. It also skips any programmed slot whose final commit unit still + * contains erased bytes, because such a slot never became visible. Any slot + * with a closed but invalid commit unit, or any committed record with invalid + * metadata or CRC, makes the bank unusable. + */ +static par_status_t par_store_flash_ee_scan_bank(uint32_t bank_base, uint32_t *p_seq, uint32_t *p_next_offset) +{ + par_store_flash_ee_bank_header_t header; + par_store_flash_ee_record_meta_t record_meta; + uint8_t line_buf[PAR_CFG_NVM_BACKEND_FLASH_EE_LINE_SIZE]; + uint8_t record_buf[PAR_STORE_FLASH_EE_RECORD_BUF_SIZE]; + const uint32_t commit_offset = par_store_flash_ee_record_commit_offset(); + par_store_flash_ee_diag_t probe = ePAR_STORE_FLASH_EE_DIAG_NONE; + uint32_t offset = PAR_STORE_FLASH_EE_HEADER_SIZE; + par_status_t status = ePAR_OK; + + status = par_store_flash_ee_port_read(bank_base, sizeof(header), (uint8_t *)&header); + if (ePAR_OK != status) + { + return status; + } + + probe = par_store_flash_ee_probe_header(&header); + if (ePAR_STORE_FLASH_EE_DIAG_NONE != probe) + { + par_store_flash_ee_set_diag(probe); + return ePAR_ERROR_NVM; + } + + while ((offset + g_par_store_flash_ee_ctx.record_size) <= g_par_store_flash_ee_ctx.bank_size) + { + status = par_store_flash_ee_port_read(bank_base + offset, + g_par_store_flash_ee_ctx.record_size, + record_buf); + if (ePAR_OK != status) + { + return status; + } + + if (true == par_store_flash_ee_is_erased_block(record_buf, g_par_store_flash_ee_ctx.record_size)) + { + break; + } + + if (false == par_store_flash_ee_is_valid_commit_unit(&record_buf[commit_offset])) + { + if (true == par_store_flash_ee_is_uncommitted_record_slot(record_buf)) + { + if (ePAR_STORE_FLASH_EE_DIAG_RECORD_TAIL != g_par_store_flash_ee_ctx.diag) + { + par_store_flash_ee_set_diag(ePAR_STORE_FLASH_EE_DIAG_RECORD_TAIL); + } + offset += g_par_store_flash_ee_ctx.record_size; + continue; + } + + par_store_flash_ee_set_diag(ePAR_STORE_FLASH_EE_DIAG_RECORD_MAGIC); + return ePAR_ERROR_NVM; + } + + par_store_flash_ee_parse_record_meta(record_buf, &record_meta); + if ((record_meta.line_index >= g_par_store_flash_ee_ctx.line_count) || + (record_meta.payload_size != g_par_store_flash_ee_ctx.line_size)) + { + par_store_flash_ee_set_diag(ePAR_STORE_FLASH_EE_DIAG_RECORD_RANGE); + return ePAR_ERROR_NVM; + } + + (void)memcpy(line_buf, record_buf, g_par_store_flash_ee_ctx.line_size); + if (par_store_flash_ee_calc_record_crc(line_buf, + record_meta.payload_size, + record_meta.line_index) != record_meta.record_crc) + { + par_store_flash_ee_set_diag(ePAR_STORE_FLASH_EE_DIAG_RECORD_CRC); + return ePAR_ERROR_NVM; + } + + offset += g_par_store_flash_ee_ctx.record_size; + } + + *p_seq = header.seq; + *p_next_offset = offset; + return ePAR_OK; +} + +/** + * @brief Format one bank header in RAM. + * + * @param[out] p_header Destination header. + * @param[in] seq Bank sequence number. + * @param[in] state Header state value. + */ +static void par_store_flash_ee_build_header(par_store_flash_ee_bank_header_t *p_header, uint32_t seq, uint32_t state) +{ + (void)memset(p_header, 0xFF, sizeof(*p_header)); + p_header->magic = PAR_STORE_FLASH_EE_HEADER_MAGIC; + p_header->version = PAR_CFG_NVM_BACKEND_FLASH_EE_VERSION; + p_header->logical_size = g_par_store_flash_ee_ctx.logical_size; + p_header->line_size = g_par_store_flash_ee_ctx.line_size; + p_header->record_size = g_par_store_flash_ee_ctx.record_size; + p_header->bank_size = g_par_store_flash_ee_ctx.bank_size; + p_header->seq = seq; + p_header->state = state; + p_header->cfg_crc = par_store_flash_ee_calc_header_crc(p_header); +} + +/** + * @brief Prepare one empty bank for later record appends. + * + * @param[in] bank_base Bank base offset. + * @param[in] seq Sequence number to place into the new header. + * @return ePAR_OK on success, otherwise an error code. + */ +static par_status_t par_store_flash_ee_prepare_bank(uint32_t bank_base, uint32_t seq) +{ + par_store_flash_ee_bank_header_t header; + par_status_t status = ePAR_OK; + + status = par_store_flash_ee_port_erase(bank_base, g_par_store_flash_ee_ctx.bank_size); + if (ePAR_OK != status) + { + return status; + } + + par_store_flash_ee_build_header(&header, seq, PAR_STORE_FLASH_EE_HEADER_PREPARE); + status = par_store_flash_ee_port_program(bank_base, sizeof(header), (const uint8_t *)&header); + return status; +} + +/** + * @brief Activate one prepared bank by patching the state word. + * + * @param[in] bank_base Bank base offset. + * @return ePAR_OK on success, otherwise an error code. + */ +static par_status_t par_store_flash_ee_activate_bank(uint32_t bank_base) +{ + uint8_t state_patch[PAR_STORE_FLASH_EE_STATE_PATCH_BUF_SIZE]; + const uint32_t state_addr = bank_base + (uint32_t)offsetof(par_store_flash_ee_bank_header_t, state); + const uint32_t patch_addr = state_addr - (state_addr % g_par_store_flash_ee_ctx.program_size); + const uint32_t patch_offset = state_addr - patch_addr; + const uint32_t patch_size = (((patch_offset + (uint32_t)sizeof(uint32_t)) + g_par_store_flash_ee_ctx.program_size - 1u) / + g_par_store_flash_ee_ctx.program_size) * + g_par_store_flash_ee_ctx.program_size; + const uint32_t active_state = PAR_STORE_FLASH_EE_HEADER_ACTIVE; + par_status_t status = ePAR_OK; + + status = par_store_flash_ee_port_read(patch_addr, patch_size, state_patch); + if (ePAR_OK != status) + { + return status; + } + + (void)memcpy(&state_patch[patch_offset], &active_state, sizeof(active_state)); + + return par_store_flash_ee_port_program(patch_addr, + patch_size, + state_patch); +} + +/** + * @brief Resolve one logical line from the active flash bank. + * + * @param[in] line_index Logical line index. + * @param[out] p_line_buf Destination line buffer. + * @return ePAR_OK on success, otherwise an error code. + */ +static par_status_t par_store_flash_ee_resolve_line_from_bank(uint32_t line_index, uint8_t *p_line_buf) +{ + par_store_flash_ee_record_meta_t record_meta; + uint8_t record_buf[PAR_STORE_FLASH_EE_RECORD_BUF_SIZE]; + const uint32_t commit_offset = par_store_flash_ee_record_commit_offset(); + par_status_t status = ePAR_OK; + uint32_t offset = g_par_store_flash_ee_ctx.active_next_offset; + + (void)memset(p_line_buf, PAR_STORE_FLASH_EE_ERASE_VALUE, g_par_store_flash_ee_ctx.line_size); + + while (offset > PAR_STORE_FLASH_EE_HEADER_SIZE) + { + offset -= g_par_store_flash_ee_ctx.record_size; + + status = par_store_flash_ee_port_read(g_par_store_flash_ee_ctx.active_bank_base + offset, + g_par_store_flash_ee_ctx.record_size, + record_buf); + if (ePAR_OK != status) + { + return status; + } + + if (false == par_store_flash_ee_is_valid_commit_unit(&record_buf[commit_offset])) + { + continue; + } + + par_store_flash_ee_parse_record_meta(record_buf, &record_meta); + if ((record_meta.line_index != line_index) || + (record_meta.payload_size != g_par_store_flash_ee_ctx.line_size)) + { + continue; + } + + (void)memcpy(p_line_buf, record_buf, g_par_store_flash_ee_ctx.line_size); + if (par_store_flash_ee_calc_record_crc(p_line_buf, + record_meta.payload_size, + record_meta.line_index) == record_meta.record_crc) + { + return ePAR_OK; + } + } + + return ePAR_OK; +} + +/** + * @brief Resolve one logical line from the current live backend view. + + * + * @details The backend first checks whether the requested line belongs to the + * currently loaded cache window. If yes, the cache line is returned directly + * because it already contains any staged updates. Otherwise the function walks + * the committed append log of the active bank backwards until it finds the + * newest record for the line. + * + * @param[in] line_index Logical line index. + * @param[out] p_line_buf Destination line buffer. + * @return ePAR_OK on success, otherwise an error code. + */ +static par_status_t par_store_flash_ee_resolve_live_line(uint32_t line_index, uint8_t *p_line_buf) +{ + uint32_t line_addr = line_index * g_par_store_flash_ee_ctx.line_size; + + if ((true == g_par_store_flash_ee_ctx.cache_valid) && + (line_addr >= g_par_store_flash_ee_ctx.cache_base) && + ((line_addr + g_par_store_flash_ee_ctx.line_size) <= + (g_par_store_flash_ee_ctx.cache_base + g_par_store_flash_ee_ctx.cache_size))) + { + uint32_t cache_offset = line_addr - g_par_store_flash_ee_ctx.cache_base; + (void)memcpy(p_line_buf, &g_par_store_flash_ee_cache[cache_offset], g_par_store_flash_ee_ctx.line_size); + return ePAR_OK; + } + + return par_store_flash_ee_resolve_line_from_bank(line_index, p_line_buf); +} + +/** + * @brief Read bytes directly from the current live backend view. + * + * @details This helper reconstructs bytes line by line without moving the + * active cache window. It is used when a dirty cache window already exists and + * the caller needs to read another logical window without committing pending + * updates. + * + * @param[in] addr Start address inside the logical address space. + * @param[in] size Number of bytes to read. + * @param[out] p_buf Destination buffer. + * @return ePAR_OK on success, otherwise an error code. + */ +static par_status_t par_store_flash_ee_read_live_range(uint32_t addr, uint32_t size, uint8_t *p_buf) +{ + par_status_t status = ePAR_OK; + uint8_t line_buf[PAR_CFG_NVM_BACKEND_FLASH_EE_LINE_SIZE]; + uint32_t remaining = size; + uint32_t cur_addr = addr; + uint8_t *p_dst = p_buf; + + while (remaining > 0u) + { + const uint32_t line_index = cur_addr / g_par_store_flash_ee_ctx.line_size; + const uint32_t line_offset = cur_addr % g_par_store_flash_ee_ctx.line_size; + uint32_t chunk = g_par_store_flash_ee_ctx.line_size - line_offset; + + status = par_store_flash_ee_resolve_live_line(line_index, line_buf); + if (ePAR_OK != status) + { + return status; + } + + if (chunk > remaining) + { + chunk = remaining; + } + + (void)memcpy(p_dst, &line_buf[line_offset], chunk); + remaining -= chunk; + cur_addr += chunk; + p_dst += chunk; + } + + return ePAR_OK; +} + +/** + * @brief Return the cache-window base that covers one logical address. + * + * @param[in] addr Logical address inside the emulated EEPROM space. + * @return Cache-window base address. + */ +static uint32_t par_store_flash_ee_window_base(uint32_t addr) +{ + return (addr / PAR_CFG_NVM_BACKEND_FLASH_EE_CACHE_SIZE) * PAR_CFG_NVM_BACKEND_FLASH_EE_CACHE_SIZE; +} + +/** + * @brief Return true when one logical range stays inside the emulated EEPROM space. + * + * @param[in] addr Start address inside the logical address space. + * @param[in] size Number of bytes in the request. + * @return true when the full range is valid; otherwise false. + */ +static bool par_store_flash_ee_range_is_valid(uint32_t addr, uint32_t size) +{ + if ((0u == size) || (addr >= g_par_store_flash_ee_ctx.logical_size)) + { + return false; + } + + return (size <= (g_par_store_flash_ee_ctx.logical_size - addr)); +} + +/** + * @brief Append one fixed-size line record into the target bank. + * + * @details The function writes the payload and metadata first, leaves the + * dedicated commit unit erased, and then programs that commit unit last. A + * record therefore becomes visible only after the final commit write succeeds. + * + * @param[in] bank_base Bank base offset. + * @param[in,out] p_next_offset Current append offset. Advanced once the + * slot payload and metadata have been programmed, even if the final commit + * unit write later fails. + * @param[in] line_index Logical line index. + * @param[in] p_line_buf Payload bytes for this logical line. + * @return ePAR_OK on success, otherwise an error code. + */ +static par_status_t par_store_flash_ee_append_record(uint32_t bank_base, + uint32_t *p_next_offset, + uint32_t line_index, + const uint8_t *p_line_buf) +{ + uint8_t record_buf[PAR_STORE_FLASH_EE_RECORD_BUF_SIZE]; + par_store_flash_ee_record_meta_t record_meta; + const uint32_t meta_offset = par_store_flash_ee_record_meta_offset(); + const uint32_t commit_offset = par_store_flash_ee_record_commit_offset(); + + if ((*p_next_offset + g_par_store_flash_ee_ctx.record_size) > g_par_store_flash_ee_ctx.bank_size) + { + par_store_flash_ee_set_diag(ePAR_STORE_FLASH_EE_DIAG_CAPACITY); + return ePAR_ERROR_NVM; + } + + (void)memset(record_buf, 0xFF, g_par_store_flash_ee_ctx.record_size); + (void)memcpy(record_buf, p_line_buf, g_par_store_flash_ee_ctx.line_size); + + record_meta.line_index = line_index; + record_meta.payload_size = (uint16_t)g_par_store_flash_ee_ctx.line_size; + record_meta.record_crc = par_store_flash_ee_calc_record_crc(p_line_buf, + (uint16_t)g_par_store_flash_ee_ctx.line_size, + line_index); + record_meta.reserved = 0xFFFFFFFFu; + (void)memcpy(&record_buf[meta_offset], &record_meta, sizeof(record_meta)); + + { + const uint32_t slot_offset = *p_next_offset; + + if (ePAR_OK != par_store_flash_ee_port_program(bank_base + slot_offset, + commit_offset, + record_buf)) + { + return ePAR_ERROR_NVM; + } + + *p_next_offset += g_par_store_flash_ee_ctx.record_size; + + par_store_flash_ee_build_commit_unit(&record_buf[commit_offset]); + if (ePAR_OK != par_store_flash_ee_port_program(bank_base + slot_offset + commit_offset, + g_par_store_flash_ee_ctx.program_size, + &record_buf[commit_offset])) + { + return ePAR_ERROR_NVM; + } + } + + return ePAR_OK; +} + +/** + * @brief Flush the current cache window by appending all dirty lines. + * + * @details The cache window is scanned line by line. Each dirty line is + * converted into one append record and written to the current active bank in + * ascending line order. The cache contents remain valid after the flush, but + * the dirty bitmap is cleared because the committed flash state now matches + * the cached state for this window. + * + * @return ePAR_OK on success, otherwise an error code. + */ +static par_status_t par_store_flash_ee_flush_cache(void) +{ + par_status_t status = ePAR_OK; + uint32_t cache_line = 0u; + + if ((false == g_par_store_flash_ee_ctx.cache_valid) || (false == par_store_flash_ee_has_dirty())) + { + return ePAR_OK; + } + + for (cache_line = 0u; cache_line < (g_par_store_flash_ee_ctx.cache_size / g_par_store_flash_ee_ctx.line_size); ++cache_line) + { + if (false == par_store_flash_ee_is_dirty(cache_line)) + { + continue; + } + + if ((g_par_store_flash_ee_ctx.active_next_offset + g_par_store_flash_ee_ctx.record_size) > g_par_store_flash_ee_ctx.bank_size) + { + return ePAR_ERROR_NVM; + } + + status = par_store_flash_ee_append_record(g_par_store_flash_ee_ctx.active_bank_base, + &g_par_store_flash_ee_ctx.active_next_offset, + (g_par_store_flash_ee_ctx.cache_base / g_par_store_flash_ee_ctx.line_size) + cache_line, + &g_par_store_flash_ee_cache[cache_line * g_par_store_flash_ee_ctx.line_size]); + if (ePAR_OK != status) + { + return status; + } + } + + par_store_flash_ee_clear_dirty(); + return ePAR_OK; +} + +/** + * @brief Load one aligned cache window from the live backend view. + * + * @details The loader only reconstructs the requested window in RAM. It never + * flushes dirty data implicitly. Callers that try to move away from a dirty + * cache window must either sync first or keep reading through the live view + * helper so pending updates stay staged in RAM. + * + * @param[in] window_base Start address of the new cache window. + * @return ePAR_OK on success, otherwise an error code. + */ +static par_status_t par_store_flash_ee_load_cache(uint32_t window_base) +{ + par_status_t status = ePAR_OK; + uint32_t line = 0u; + + if ((window_base >= g_par_store_flash_ee_ctx.logical_size) || + (0u != (window_base % g_par_store_flash_ee_ctx.line_size))) + { + par_store_flash_ee_set_diag(ePAR_STORE_FLASH_EE_DIAG_CACHE_WINDOW); + return ePAR_ERROR_PARAM; + } + + if ((true == g_par_store_flash_ee_ctx.cache_valid) && + (g_par_store_flash_ee_ctx.cache_base == window_base)) + { + return ePAR_OK; + } + + if ((true == g_par_store_flash_ee_ctx.cache_valid) && (true == par_store_flash_ee_has_dirty())) + { + par_store_flash_ee_set_diag(ePAR_STORE_FLASH_EE_DIAG_CACHE_DIRTY_WINDOW); + return ePAR_ERROR_NVM; + } + + g_par_store_flash_ee_ctx.cache_base = window_base; + g_par_store_flash_ee_ctx.cache_size = g_par_store_flash_ee_ctx.logical_size - window_base; + if (g_par_store_flash_ee_ctx.cache_size > PAR_CFG_NVM_BACKEND_FLASH_EE_CACHE_SIZE) + { + g_par_store_flash_ee_ctx.cache_size = PAR_CFG_NVM_BACKEND_FLASH_EE_CACHE_SIZE; + } + + (void)memset(g_par_store_flash_ee_cache, PAR_STORE_FLASH_EE_ERASE_VALUE, g_par_store_flash_ee_ctx.cache_size); + for (line = 0u; line < (g_par_store_flash_ee_ctx.cache_size / g_par_store_flash_ee_ctx.line_size); ++line) + { + status = par_store_flash_ee_resolve_line_from_bank((window_base / g_par_store_flash_ee_ctx.line_size) + line, + &g_par_store_flash_ee_cache[line * g_par_store_flash_ee_ctx.line_size]); + if (ePAR_OK != status) + { + return status; + } + } + + g_par_store_flash_ee_ctx.cache_valid = true; + par_store_flash_ee_clear_dirty(); + return ePAR_OK; +} + +/** + * @brief Ensure the requested cache window is ready for staging. + * + * @details The backend still keeps only one dirty cache window in RAM at a + * time. When write or erase processing needs to move staging to another + * window, this helper synchronizes the current dirty window first and then + * loads the next window. + * + * @param[in] window_base Start address of the required cache window. + * @return ePAR_OK on success, otherwise an error code. + */ +static par_status_t par_store_flash_ee_prepare_cache_window(uint32_t window_base) +{ + par_status_t status = ePAR_OK; + + if ((true == g_par_store_flash_ee_ctx.cache_valid) && + (g_par_store_flash_ee_ctx.cache_base == window_base)) + { + return ePAR_OK; + } + + if ((true == g_par_store_flash_ee_ctx.cache_valid) && + (true == par_store_flash_ee_has_dirty()) && + (g_par_store_flash_ee_ctx.cache_base != window_base)) + { + status = par_store_flash_ee_sync(); + if (ePAR_OK != status) + { + return status; + } + } + + return par_store_flash_ee_load_cache(window_base); +} + +/** + * @brief Commit the currently staged cache window when it is dirty. + * + * @details This helper is used at the end of logical write and erase requests + * so a successful backend call never leaves the final modified cache window in + * RAM only. The backend may still synchronize earlier windows while processing + * a multi-window request, but returning @ref ePAR_OK guarantees that the last + * staged window is also durable in flash. Multi-window requests are still not + * transactional: an error reported after an earlier window was synchronized + * does not roll that earlier committed window back. + * + * @return ePAR_OK on success, otherwise an error code. + */ +static par_status_t par_store_flash_ee_commit_staged_cache(void) +{ + if ((false == g_par_store_flash_ee_ctx.cache_valid) || + (false == par_store_flash_ee_has_dirty())) + { + return ePAR_OK; + } + + return par_store_flash_ee_sync(); +} + +/** + * @brief Build a compacted checkpoint inside the inactive bank. + * + * @return ePAR_OK on success, otherwise an error code. + */ +static par_status_t par_store_flash_ee_checkpoint(void) +{ + par_status_t status = ePAR_OK; + uint32_t line_index = 0u; + uint32_t next_offset = PAR_STORE_FLASH_EE_HEADER_SIZE; + uint32_t target_bank = g_par_store_flash_ee_ctx.inactive_bank_base; + uint8_t line_buf[PAR_CFG_NVM_BACKEND_FLASH_EE_LINE_SIZE]; + + status = par_store_flash_ee_prepare_bank(target_bank, g_par_store_flash_ee_ctx.active_seq + 1u); + if (ePAR_OK != status) + { + par_store_flash_ee_set_diag(ePAR_STORE_FLASH_EE_DIAG_CHECKPOINT); + return status; + } + + for (line_index = 0u; line_index < g_par_store_flash_ee_ctx.line_count; ++line_index) + { + status = par_store_flash_ee_resolve_live_line(line_index, line_buf); + if (ePAR_OK != status) + { + par_store_flash_ee_set_diag(ePAR_STORE_FLASH_EE_DIAG_CHECKPOINT); + return status; + } + + if (true == par_store_flash_ee_is_erased_block(line_buf, g_par_store_flash_ee_ctx.line_size)) + { + continue; + } + + status = par_store_flash_ee_append_record(target_bank, &next_offset, line_index, line_buf); + if (ePAR_OK != status) + { + par_store_flash_ee_set_diag(ePAR_STORE_FLASH_EE_DIAG_CHECKPOINT); + return status; + } + } + + status = par_store_flash_ee_activate_bank(target_bank); + if (ePAR_OK != status) + { + par_store_flash_ee_set_diag(ePAR_STORE_FLASH_EE_DIAG_CHECKPOINT); + return status; + } + + g_par_store_flash_ee_ctx.active_bank_base = target_bank; + g_par_store_flash_ee_ctx.inactive_bank_base = (0u == target_bank) ? g_par_store_flash_ee_ctx.bank_size : 0u; + g_par_store_flash_ee_ctx.active_seq += 1u; + g_par_store_flash_ee_ctx.active_next_offset = next_offset; + par_store_flash_ee_clear_dirty(); + PAR_WARN_PRINT("PAR_FLASH_EE: checkpoint complete, seq=%lu, next_offset=%lu", + (unsigned long)g_par_store_flash_ee_ctx.active_seq, + (unsigned long)g_par_store_flash_ee_ctx.active_next_offset); + return ePAR_OK; +} + +/** + * @brief Ensure the active bank has room for at least one more append record. + * + * @return ePAR_OK on success, otherwise an error code. + */ +static par_status_t par_store_flash_ee_ensure_capacity(uint32_t needed_records) +{ + if ((g_par_store_flash_ee_ctx.active_next_offset + (needed_records * g_par_store_flash_ee_ctx.record_size)) <= + g_par_store_flash_ee_ctx.bank_size) + { + return ePAR_OK; + } + + return par_store_flash_ee_checkpoint(); +} + +/** + * @brief Initialize the generic flash-emulated EEPROM backend. + * + * @return ePAR_OK on success, otherwise an error code. + */ +static par_status_t par_store_flash_ee_init(void) +{ + par_status_t status = ePAR_OK; + par_status_t scan_status0 = ePAR_ERROR_NVM; + par_status_t scan_status1 = ePAR_ERROR_NVM; + bool port_is_init = false; + uint32_t seq0 = 0u; + uint32_t seq1 = 0u; + uint32_t next0 = 0u; + uint32_t next1 = 0u; + bool valid0 = false; + bool valid1 = false; + par_store_flash_ee_diag_t scan_diag0 = ePAR_STORE_FLASH_EE_DIAG_NONE; + par_store_flash_ee_diag_t scan_diag1 = ePAR_STORE_FLASH_EE_DIAG_NONE; + + if ((false == g_par_store_flash_ee_ctx.is_bound) || (NULL == g_par_store_flash_ee_ctx.p_port_api)) + { + par_store_flash_ee_set_diag(ePAR_STORE_FLASH_EE_DIAG_PORT_NOT_BOUND); + return ePAR_ERROR_INIT; + } + + if (true == g_par_store_flash_ee_ctx.is_init) + { + return ePAR_OK; + } + + status = g_par_store_flash_ee_ctx.p_port_api->init(g_par_store_flash_ee_ctx.p_port_ctx); + if (ePAR_OK != status) + { + par_store_flash_ee_set_diag(ePAR_STORE_FLASH_EE_DIAG_PORT_INIT); + return ePAR_ERROR_INIT; + } + + status = g_par_store_flash_ee_ctx.p_port_api->is_init(g_par_store_flash_ee_ctx.p_port_ctx, &port_is_init); + if ((ePAR_OK != status) || (false == port_is_init)) + { + par_store_flash_ee_set_diag(ePAR_STORE_FLASH_EE_DIAG_PORT_INIT); + status = ePAR_ERROR_INIT; + goto fail_after_port_init; + } + + g_par_store_flash_ee_ctx.region_size = g_par_store_flash_ee_ctx.p_port_api->get_region_size(g_par_store_flash_ee_ctx.p_port_ctx); + g_par_store_flash_ee_ctx.erase_size = g_par_store_flash_ee_ctx.p_port_api->get_erase_size(g_par_store_flash_ee_ctx.p_port_ctx); + g_par_store_flash_ee_ctx.program_size = g_par_store_flash_ee_ctx.p_port_api->get_program_size(g_par_store_flash_ee_ctx.p_port_ctx); + + status = par_store_flash_ee_validate_geometry(); + if (ePAR_OK != status) + { + goto fail_after_port_init; + } + + par_store_flash_ee_set_diag(ePAR_STORE_FLASH_EE_DIAG_NONE); + scan_status0 = par_store_flash_ee_scan_bank(par_store_flash_ee_bank_base(0u), &seq0, &next0); + scan_diag0 = g_par_store_flash_ee_ctx.diag; + if (ePAR_OK == scan_status0) + { + valid0 = true; + } + + par_store_flash_ee_set_diag(ePAR_STORE_FLASH_EE_DIAG_NONE); + scan_status1 = par_store_flash_ee_scan_bank(par_store_flash_ee_bank_base(1u), &seq1, &next1); + scan_diag1 = g_par_store_flash_ee_ctx.diag; + if (ePAR_OK == scan_status1) + { + valid1 = true; + } + + if ((false == valid0) && (false == valid1)) + { + if ((false == par_store_flash_ee_scan_diag_allows_reformat(scan_diag0)) || + (false == par_store_flash_ee_scan_diag_allows_reformat(scan_diag1))) + { + par_store_flash_ee_set_diag((false == par_store_flash_ee_scan_diag_allows_reformat(scan_diag0)) ? scan_diag0 : scan_diag1); + PAR_ERR_PRINT("PAR_FLASH_EE: refusing auto-format after fatal bank scan failure, bank0_diag=%u bank1_diag=%u", + (unsigned)scan_diag0, + (unsigned)scan_diag1); + status = ePAR_ERROR_INIT; + goto fail_after_port_init; + } + + status = par_store_flash_ee_prepare_bank(par_store_flash_ee_bank_base(0u), 1u); + if (ePAR_OK != status) + { + goto fail_after_port_init; + } + + status = par_store_flash_ee_activate_bank(par_store_flash_ee_bank_base(0u)); + if (ePAR_OK != status) + { + goto fail_after_port_init; + } + + g_par_store_flash_ee_ctx.active_bank_base = par_store_flash_ee_bank_base(0u); + g_par_store_flash_ee_ctx.inactive_bank_base = par_store_flash_ee_bank_base(1u); + g_par_store_flash_ee_ctx.active_seq = 1u; + g_par_store_flash_ee_ctx.active_next_offset = PAR_STORE_FLASH_EE_HEADER_SIZE; + par_store_flash_ee_set_diag(ePAR_STORE_FLASH_EE_DIAG_NONE); + PAR_WARN_PRINT("PAR_FLASH_EE: formatted empty persistence banks"); + } + else if ((true == valid0) && ((false == valid1) || (seq0 >= seq1))) + { + g_par_store_flash_ee_ctx.active_bank_base = par_store_flash_ee_bank_base(0u); + g_par_store_flash_ee_ctx.inactive_bank_base = par_store_flash_ee_bank_base(1u); + g_par_store_flash_ee_ctx.active_seq = seq0; + g_par_store_flash_ee_ctx.active_next_offset = next0; + par_store_flash_ee_set_diag(scan_diag0); + } + else + { + g_par_store_flash_ee_ctx.active_bank_base = par_store_flash_ee_bank_base(1u); + g_par_store_flash_ee_ctx.inactive_bank_base = par_store_flash_ee_bank_base(0u); + g_par_store_flash_ee_ctx.active_seq = seq1; + g_par_store_flash_ee_ctx.active_next_offset = next1; + par_store_flash_ee_set_diag(scan_diag1); + } + + g_par_store_flash_ee_ctx.cache_valid = false; + g_par_store_flash_ee_ctx.is_init = true; + par_store_flash_ee_clear_dirty(); + PAR_DBG_PRINT("PAR_FLASH_EE: init ok, port=%s, bank_size=%lu, logical_size=%lu, line_size=%lu", + (NULL != g_par_store_flash_ee_ctx.p_port_api->get_name) + ? g_par_store_flash_ee_ctx.p_port_api->get_name(g_par_store_flash_ee_ctx.p_port_ctx) + : "unknown", + (unsigned long)g_par_store_flash_ee_ctx.bank_size, + (unsigned long)g_par_store_flash_ee_ctx.logical_size, + (unsigned long)g_par_store_flash_ee_ctx.line_size); + return ePAR_OK; + +fail_after_port_init: + par_store_flash_ee_cleanup_failed_init(); + return status; +} + +/** + * @brief Deinitialize the generic flash-emulated EEPROM backend. + * + * @return ePAR_OK on success, otherwise an error code. + */ +static par_status_t par_store_flash_ee_deinit(void) +{ + par_status_t status = ePAR_OK; + + if ((false == g_par_store_flash_ee_ctx.is_init) || (NULL == g_par_store_flash_ee_ctx.p_port_api)) + { + return ePAR_OK; + } + + status = par_store_flash_ee_sync(); + if (ePAR_OK != status) + { + return status; + } + + status = g_par_store_flash_ee_ctx.p_port_api->deinit(g_par_store_flash_ee_ctx.p_port_ctx); + if (ePAR_OK != status) + { + par_store_flash_ee_set_diag(ePAR_STORE_FLASH_EE_DIAG_PORT_INIT); + return ePAR_ERROR; + } + + g_par_store_flash_ee_ctx.is_init = false; + g_par_store_flash_ee_ctx.cache_valid = false; + return par_store_flash_ee_complete_status(ePAR_OK); +} + +/** + * @brief Report whether the generic backend is initialized. + * + * @param[out] p_is_init Receives the initialization state. + * @return ePAR_OK on success, otherwise an error code. + */ +static par_status_t par_store_flash_ee_is_init(bool *p_is_init) +{ + if (NULL == p_is_init) + { + return ePAR_ERROR_PARAM; + } + + *p_is_init = g_par_store_flash_ee_ctx.is_init; + return ePAR_OK; +} + +/** + * @brief Read bytes from the flash-emulated EEPROM logical address space. + * + * @details The read path is cache-aware. The backend calculates the cache + * window that covers the requested address range, loads that window on demand, + * and then copies bytes from the RAM cache. If another dirty window is already + * staged, reads fall back to the live-view helper so data can be reconstructed + * without committing pending updates. + * + * @param[in] addr Start address inside the logical address space. + * @param[in] size Number of bytes to read. + * @param[out] p_buf Destination buffer. + * @return ePAR_OK on success, otherwise an error code. + */ +static par_status_t par_store_flash_ee_read(const uint32_t addr, const uint32_t size, uint8_t *p_buf) +{ + par_status_t status = ePAR_OK; + uint32_t remaining = size; + uint32_t cur_addr = addr; + uint8_t *p_dst = p_buf; + + if ((NULL == p_buf) || (false == par_store_flash_ee_range_is_valid(addr, size))) + { + return ePAR_ERROR_PARAM; + } + + if (false == g_par_store_flash_ee_ctx.is_init) + { + par_store_flash_ee_set_diag(ePAR_STORE_FLASH_EE_DIAG_NOT_INIT); + return ePAR_ERROR_INIT; + } + + while (remaining > 0u) + { + uint32_t window_base = par_store_flash_ee_window_base(cur_addr); + uint32_t window_offset = cur_addr - window_base; + uint32_t chunk = 0u; + + if ((false == g_par_store_flash_ee_ctx.cache_valid) || (g_par_store_flash_ee_ctx.cache_base != window_base)) + { + if ((true == g_par_store_flash_ee_ctx.cache_valid) && + (true == par_store_flash_ee_has_dirty()) && + (g_par_store_flash_ee_ctx.cache_base != window_base)) + { + chunk = g_par_store_flash_ee_ctx.logical_size - window_base; + if (chunk > PAR_CFG_NVM_BACKEND_FLASH_EE_CACHE_SIZE) + { + chunk = PAR_CFG_NVM_BACKEND_FLASH_EE_CACHE_SIZE; + } + chunk -= window_offset; + if (chunk > remaining) + { + chunk = remaining; + } + + status = par_store_flash_ee_read_live_range(cur_addr, chunk, p_dst); + if (ePAR_OK != status) + { + return status; + } + + remaining -= chunk; + cur_addr += chunk; + p_dst += chunk; + continue; + } + + status = par_store_flash_ee_load_cache(window_base); + if (ePAR_OK != status) + { + return status; + } + } + + chunk = g_par_store_flash_ee_ctx.cache_size - window_offset; + if (chunk > remaining) + { + chunk = remaining; + } + + (void)memcpy(p_dst, &g_par_store_flash_ee_cache[window_offset], chunk); + remaining -= chunk; + cur_addr += chunk; + p_dst += chunk; + } + + return par_store_flash_ee_complete_status(ePAR_OK); +} + +/** + * @brief Write bytes into the flash-emulated EEPROM logical address space. + * + * @details The write path never programs flash directly. It loads the cache + * window that covers the target logical address, updates the bytes in RAM, and + * marks all overlapped cache lines dirty. The modified lines are later turned + * into append records during sync. Requests may span multiple cache windows. + * Before staging bytes in a new window, the backend synchronizes the current + * dirty window and then continues with the next chunk. Before returning + * success, the backend also synchronizes the final dirty window so the full + * completed request is durable in flash. Requests that span multiple cache + * windows are not transactional across those windows. + * + * @param[in] addr Start address inside the logical address space. + * @param[in] size Number of bytes to write. + * @param[in] p_buf Source buffer. + * @return ePAR_OK on success, otherwise an error code. + */ +static par_status_t par_store_flash_ee_write(const uint32_t addr, const uint32_t size, const uint8_t *p_buf) +{ + par_status_t status = ePAR_OK; + uint32_t remaining = size; + uint32_t cur_addr = addr; + const uint8_t *p_src = p_buf; + + if ((NULL == p_buf) || (false == par_store_flash_ee_range_is_valid(addr, size))) + { + return ePAR_ERROR_PARAM; + } + + if (false == g_par_store_flash_ee_ctx.is_init) + { + par_store_flash_ee_set_diag(ePAR_STORE_FLASH_EE_DIAG_NOT_INIT); + return ePAR_ERROR_INIT; + } + + while (remaining > 0u) + { + uint32_t window_base = par_store_flash_ee_window_base(cur_addr); + uint32_t window_offset = cur_addr - window_base; + uint32_t chunk = 0u; + uint32_t first_line = 0u; + uint32_t last_line = 0u; + uint32_t line = 0u; + + status = par_store_flash_ee_prepare_cache_window(window_base); + if (ePAR_OK != status) + { + return status; + } + + chunk = g_par_store_flash_ee_ctx.cache_size - window_offset; + if (chunk > remaining) + { + chunk = remaining; + } + + (void)memcpy(&g_par_store_flash_ee_cache[window_offset], p_src, chunk); + + first_line = window_offset / g_par_store_flash_ee_ctx.line_size; + last_line = (window_offset + chunk - 1u) / g_par_store_flash_ee_ctx.line_size; + for (line = first_line; line <= last_line; ++line) + { + par_store_flash_ee_mark_dirty(line); + } + + remaining -= chunk; + cur_addr += chunk; + p_src += chunk; + } + + status = par_store_flash_ee_commit_staged_cache(); + return par_store_flash_ee_complete_status(status); +} + +/** + * @brief Erase bytes inside the flash-emulated EEPROM logical address space. + * + * @details The logical erase path mirrors the write path. The target bytes in + * the RAM cache are set to the erased value and all overlapped cache lines are + * marked dirty so sync can append their new erased image later. Requests may + * span multiple cache windows. Before staging bytes in a new window, the + * backend synchronizes the current dirty window and then continues with the + * next chunk. Before returning success, the backend also synchronizes the + * final dirty window so the full completed request is durable in flash. + * Requests that span multiple cache windows are not transactional across + * those windows. + * + * @param[in] addr Start address inside the logical address space. + * @param[in] size Number of bytes to erase. + * @return ePAR_OK on success, otherwise an error code. + */ +static par_status_t par_store_flash_ee_erase(const uint32_t addr, const uint32_t size) +{ + par_status_t status = ePAR_OK; + uint32_t remaining = size; + uint32_t cur_addr = addr; + + if (false == par_store_flash_ee_range_is_valid(addr, size)) + { + return ePAR_ERROR_PARAM; + } + + if (false == g_par_store_flash_ee_ctx.is_init) + { + par_store_flash_ee_set_diag(ePAR_STORE_FLASH_EE_DIAG_NOT_INIT); + return ePAR_ERROR_INIT; + } + + while (remaining > 0u) + { + uint32_t window_base = par_store_flash_ee_window_base(cur_addr); + uint32_t window_offset = cur_addr - window_base; + uint32_t chunk = 0u; + uint32_t first_line = 0u; + uint32_t last_line = 0u; + uint32_t line = 0u; + + status = par_store_flash_ee_prepare_cache_window(window_base); + if (ePAR_OK != status) + { + return status; + } + + chunk = g_par_store_flash_ee_ctx.cache_size - window_offset; + if (chunk > remaining) + { + chunk = remaining; + } + + (void)memset(&g_par_store_flash_ee_cache[window_offset], PAR_STORE_FLASH_EE_ERASE_VALUE, chunk); + + first_line = window_offset / g_par_store_flash_ee_ctx.line_size; + last_line = (window_offset + chunk - 1u) / g_par_store_flash_ee_ctx.line_size; + for (line = first_line; line <= last_line; ++line) + { + par_store_flash_ee_mark_dirty(line); + } + + remaining -= chunk; + cur_addr += chunk; + } + + status = par_store_flash_ee_commit_staged_cache(); + return par_store_flash_ee_complete_status(status); +} + +/** + * @brief Flush staged cache data into physical flash. + * + * @details Sync first estimates how many append records are needed for the + * current dirty lines. If the active bank does not have enough free space, the + * backend performs a checkpoint into the inactive bank. After capacity is + * guaranteed, each dirty cache line is appended as one new record. + * + * @return ePAR_OK on success, otherwise an error code. + */ +static par_status_t par_store_flash_ee_sync(void) +{ + par_status_t status = ePAR_OK; + + if (false == g_par_store_flash_ee_ctx.is_init) + { + par_store_flash_ee_set_diag(ePAR_STORE_FLASH_EE_DIAG_NOT_INIT); + return ePAR_ERROR_INIT; + } + + status = par_store_flash_ee_ensure_capacity(par_store_flash_ee_count_dirty()); + if (ePAR_OK != status) + { + return status; + } + + status = par_store_flash_ee_flush_cache(); + if (ePAR_OK != status) + { + if (ePAR_OK == par_store_flash_ee_checkpoint()) + { + status = par_store_flash_ee_flush_cache(); + } + } + + return par_store_flash_ee_complete_status(status); +} + +/** + * @brief Expose the backend operations to the persistence framework. + */ +static const par_store_backend_api_t g_par_store_flash_ee_api = { + .init = par_store_flash_ee_init, + .deinit = par_store_flash_ee_deinit, + .is_init = par_store_flash_ee_is_init, + .read = par_store_flash_ee_read, + .write = par_store_flash_ee_write, + .erase = par_store_flash_ee_erase, + .sync = par_store_flash_ee_sync, + .name = "flash_ee", +}; + +/** + * @brief Bind one physical flash port to the backend core. + * + * @param[in] p_port_api Physical flash port API table. + * @param[in,out] p_port_ctx Mutable port context passed back to the adapter. + * @return ePAR_OK on success, otherwise an error code. + */ +par_status_t par_store_backend_flash_ee_bind_port(const par_store_flash_ee_port_api_t *p_port_api, void *p_port_ctx) +{ + if ((NULL == p_port_api) || (NULL == p_port_api->init) || (NULL == p_port_api->deinit) || + (NULL == p_port_api->is_init) || (NULL == p_port_api->read) || (NULL == p_port_api->program) || + (NULL == p_port_api->erase) || (NULL == p_port_api->get_region_size) || + (NULL == p_port_api->get_erase_size) || (NULL == p_port_api->get_program_size)) + { + return ePAR_ERROR_PARAM; + } + + if ((true == g_par_store_flash_ee_ctx.is_bound) && + (g_par_store_flash_ee_ctx.p_port_api == p_port_api) && + (g_par_store_flash_ee_ctx.p_port_ctx == p_port_ctx)) + { + return ePAR_OK; + } + + g_par_store_flash_ee_ctx.p_port_api = p_port_api; + g_par_store_flash_ee_ctx.p_port_ctx = p_port_ctx; + g_par_store_flash_ee_ctx.is_bound = true; + g_par_store_flash_ee_ctx.is_init = false; + g_par_store_flash_ee_ctx.cache_valid = false; + par_store_flash_ee_clear_dirty(); + return ePAR_OK; +} + +/** + * @brief Return the generic backend API table. + * + * @return Pointer to the backend API table. + */ +const par_store_backend_api_t *par_store_backend_flash_ee_get_api(void) +{ + return &g_par_store_flash_ee_api; +} + +/** + * @brief Return the latest backend diagnostic code. + * + * @return Latest backend diagnostic code. + */ +par_store_flash_ee_diag_t par_store_backend_flash_ee_get_diag(void) +{ + return g_par_store_flash_ee_ctx.diag; +} + +/** + * @brief Return the diagnostic string for one backend diagnostic code. + * + * @param[in] diag Diagnostic code to translate. + * @return Constant string representation. + */ +const char *par_store_backend_flash_ee_get_diag_str(par_store_flash_ee_diag_t diag) +{ + switch (diag) + { + case ePAR_STORE_FLASH_EE_DIAG_NONE: return "none"; + case ePAR_STORE_FLASH_EE_DIAG_PORT_NOT_BOUND: return "port_not_bound"; + case ePAR_STORE_FLASH_EE_DIAG_PORT_INIT: return "port_init"; + case ePAR_STORE_FLASH_EE_DIAG_PORT_READ: return "port_read"; + case ePAR_STORE_FLASH_EE_DIAG_PORT_PROGRAM: return "port_program"; + case ePAR_STORE_FLASH_EE_DIAG_PORT_ERASE: return "port_erase"; + case ePAR_STORE_FLASH_EE_DIAG_PORT_GEOMETRY: return "port_geometry"; + case ePAR_STORE_FLASH_EE_DIAG_CONFIG: return "config"; + case ePAR_STORE_FLASH_EE_DIAG_BANK_ERASED: return "bank_erased"; + case ePAR_STORE_FLASH_EE_DIAG_HEADER_MAGIC: return "header_magic"; + case ePAR_STORE_FLASH_EE_DIAG_HEADER_VERSION: return "header_version"; + case ePAR_STORE_FLASH_EE_DIAG_HEADER_STATE: return "header_state"; + case ePAR_STORE_FLASH_EE_DIAG_HEADER_CRC: return "header_crc"; + case ePAR_STORE_FLASH_EE_DIAG_HEADER_LAYOUT: return "header_layout"; + case ePAR_STORE_FLASH_EE_DIAG_RECORD_MAGIC: return "record_commit"; + case ePAR_STORE_FLASH_EE_DIAG_RECORD_RANGE: return "record_range"; + case ePAR_STORE_FLASH_EE_DIAG_RECORD_CRC: return "record_crc"; + case ePAR_STORE_FLASH_EE_DIAG_RECORD_TAIL: return "record_tail"; + case ePAR_STORE_FLASH_EE_DIAG_CACHE_WINDOW: return "cache_window"; + case ePAR_STORE_FLASH_EE_DIAG_CACHE_DIRTY_WINDOW: return "cache_dirty_window"; + case ePAR_STORE_FLASH_EE_DIAG_CAPACITY: return "capacity"; + case ePAR_STORE_FLASH_EE_DIAG_CHECKPOINT: return "checkpoint"; + case ePAR_STORE_FLASH_EE_DIAG_NOT_INIT: return "not_init"; + default: return "unknown"; + } +} + +#endif /* (1 == PAR_CFG_NVM_EN) && (1 == PAR_CFG_NVM_BACKEND_FLASH_EE_EN) */ diff --git a/src/persist/backend/par_store_backend_flash_ee.h b/src/persist/backend/par_store_backend_flash_ee.h new file mode 100644 index 0000000..29a494a --- /dev/null +++ b/src/persist/backend/par_store_backend_flash_ee.h @@ -0,0 +1,297 @@ +/** + * @file par_store_backend_flash_ee.h + * @brief Declare the generic flash-emulated EEPROM backend core. + * @author wdfk-prog + * @version 1.0 + * @date 2026-04-14 + * + * @copyright Copyright (c) 2026 Ziga Miklosic. Distributed under the MIT license. + * + * @details This file declares the portable execution core used by the + * flash-emulated EEPROM backend. The core does not depend on RT-Thread, + * FAL, HAL, or any other platform SDK directly. A platform adapter binds the + * core to one concrete physical flash implementation through the port API + * defined in this header. + * + * @note : + * @par Change Log: + * Date Version Author Description + * 2026-04-14 1.0 wdfk-prog first version + */ +#ifndef PAR_STORE_BACKEND_FLASH_EE_H +#define PAR_STORE_BACKEND_FLASH_EE_H + +#include +#include + +#include "par.h" +#include "persist/backend/par_store_backend.h" + +#if (1 == PAR_CFG_NVM_EN) && (1 == PAR_CFG_NVM_BACKEND_FLASH_EE_EN) + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Diagnose the latest flash-emulated EEPROM backend event. + * + * @details The backend still returns the package-level @ref par_status_t codes + * to preserve the existing persistence ABI. This diagnostic enumeration keeps a + * more specific reason code for logging, debugging, and later extension. + */ +typedef enum +{ + ePAR_STORE_FLASH_EE_DIAG_NONE = 0, /**< No diagnostic information is pending. */ + ePAR_STORE_FLASH_EE_DIAG_PORT_NOT_BOUND, /**< No physical flash port was bound to the core. */ + ePAR_STORE_FLASH_EE_DIAG_PORT_INIT, /**< Physical port initialization failed. */ + ePAR_STORE_FLASH_EE_DIAG_PORT_READ, /**< Physical flash read failed. */ + ePAR_STORE_FLASH_EE_DIAG_PORT_PROGRAM, /**< Physical flash program failed. */ + ePAR_STORE_FLASH_EE_DIAG_PORT_ERASE, /**< Physical flash erase failed. */ + ePAR_STORE_FLASH_EE_DIAG_PORT_GEOMETRY, /**< Physical flash geometry is invalid or unsupported. */ + ePAR_STORE_FLASH_EE_DIAG_CONFIG, /**< Compile-time configuration is invalid. */ + ePAR_STORE_FLASH_EE_DIAG_BANK_ERASED, /**< Flash bank is fully erased and contains no valid header. */ + ePAR_STORE_FLASH_EE_DIAG_HEADER_MAGIC, /**< Flash bank header magic does not match. */ + ePAR_STORE_FLASH_EE_DIAG_HEADER_VERSION, /**< Flash bank header version is not supported. */ + ePAR_STORE_FLASH_EE_DIAG_HEADER_STATE, /**< Flash bank header state is not active. */ + ePAR_STORE_FLASH_EE_DIAG_HEADER_CRC, /**< Flash bank header CRC is invalid. */ + ePAR_STORE_FLASH_EE_DIAG_HEADER_LAYOUT, /**< Flash bank header geometry does not match the live configuration. */ + ePAR_STORE_FLASH_EE_DIAG_RECORD_MAGIC, /**< Append record commit marker is closed but invalid. */ + ePAR_STORE_FLASH_EE_DIAG_RECORD_RANGE, /**< Append record line index exceeds the logical address space. */ + ePAR_STORE_FLASH_EE_DIAG_RECORD_CRC, /**< Append record payload or semantic metadata CRC is invalid. */ + ePAR_STORE_FLASH_EE_DIAG_RECORD_TAIL, /**< One or more uncommitted append slots were skipped. */ + ePAR_STORE_FLASH_EE_DIAG_CACHE_WINDOW, /**< Cache window alignment or bounds are invalid. */ + ePAR_STORE_FLASH_EE_DIAG_CACHE_DIRTY_WINDOW, /**< A dirty cache window blocks switching to another window before sync. */ + ePAR_STORE_FLASH_EE_DIAG_CAPACITY, /**< Active bank does not have enough capacity. */ + ePAR_STORE_FLASH_EE_DIAG_CHECKPOINT, /**< Bank checkpoint or rollover failed. */ + ePAR_STORE_FLASH_EE_DIAG_NOT_INIT /**< Backend was used before successful initialization. */ +} par_store_flash_ee_diag_t; + +/** + * @brief Describe one concrete physical flash port. + * + * @details All offsets are relative to the beginning of the persistence region + * owned by the backend adapter. The generic core splits that region into two + * banks and never accesses flash outside the supplied region. + * + * @note The backend uses a data-first, commit-last append protocol. Only + * fully committed records are visible during scan and recovery. The backend + * still keeps only one dirty cache window at a time, but write and erase + * operations may span multiple cache windows. When staging moves to a new + * window, the current dirty window is synchronized first, and the final dirty + * window is synchronized before a successful write or erase call returns. + * Multi-window requests are therefore durable on success, but they are not + * transactional across windows when a later step fails. Integrations should + * size the cache so common parameter objects fit within one window, and should + * avoid splitting one must-stay-consistent parameter group across multiple + * independently committed windows. + */ +typedef struct +{ + /** + * @brief Initialize the physical flash port. + * + * @param[in,out] p_ctx Mutable port context owned by the adapter. + * @return ePAR_OK on success, otherwise a package error code. + */ + par_status_t (*init)(void *p_ctx); + + /** + * @brief Deinitialize the physical flash port. + * + * @param[in,out] p_ctx Mutable port context owned by the adapter. + * @return ePAR_OK on success, otherwise a package error code. + */ + par_status_t (*deinit)(void *p_ctx); + + /** + * @brief Report whether the physical flash port is initialized. + * + * @param[in] p_ctx Immutable port context. + * @param[out] p_is_init Receives the initialization state. + * @return ePAR_OK on success, otherwise a package error code. + */ + par_status_t (*is_init)(const void *p_ctx, bool *p_is_init); + + /** + * @brief Read raw bytes from the physical flash region. + * + * @param[in] p_ctx Immutable port context. + * @param[in] addr Byte offset relative to the bound persistence region. + * @param[in] size Number of bytes to read. + * @param[out] p_buf Destination buffer. + * @return ePAR_OK on success, otherwise a package error code. + */ + par_status_t (*read)(const void *p_ctx, uint32_t addr, uint32_t size, uint8_t *p_buf); + + /** + * @brief Program raw bytes into the physical flash region. + * + * @param[in] p_ctx Immutable port context. + * @param[in] addr Byte offset relative to the bound persistence region. + * @param[in] size Number of bytes to program. + * @param[in] p_buf Source buffer. + * @return ePAR_OK on success, otherwise a package error code. + */ + par_status_t (*program)(const void *p_ctx, uint32_t addr, uint32_t size, const uint8_t *p_buf); + + /** + * @brief Erase raw bytes inside the physical flash region. + * + * @param[in] p_ctx Immutable port context. + * @param[in] addr Byte offset relative to the bound persistence region. + * @param[in] size Number of bytes to erase. + * @return ePAR_OK on success, otherwise a package error code. + */ + par_status_t (*erase)(const void *p_ctx, uint32_t addr, uint32_t size); + + /** + * @brief Return the total persistence-region size in bytes. + * + * @param[in] p_ctx Immutable port context. + * @return Region size in bytes. + */ + uint32_t (*get_region_size)(const void *p_ctx); + + /** + * @brief Return the physical flash erase granularity in bytes. + * + * @param[in] p_ctx Immutable port context. + * @return Erase granularity in bytes. + */ + uint32_t (*get_erase_size)(const void *p_ctx); + + /** + * @brief Return the physical flash program granularity in bytes. + * + * @param[in] p_ctx Immutable port context. + * @return Program granularity in bytes. + */ + uint32_t (*get_program_size)(const void *p_ctx); + + /** + * @brief Return a short port name for diagnostics. + * + * @param[in] p_ctx Immutable port context. + * @return Null-terminated name string, or NULL when unavailable. + */ + const char *(*get_name)(const void *p_ctx); +} par_store_flash_ee_port_api_t; + +#if (1 == PAR_CFG_NVM_BACKEND_FLASH_EE_PORT_NATIVE_EN) +/** + * @brief Initialize the user-supplied native flash port. + * + * @details The native adapter expects the application or BSP to provide strong + * definitions for the required operational and geometry hooks declared in this + * header. Missing required definitions should fail at link time rather than + * falling back to runtime stub behavior. The package may still provide benign + * defaults for optional helper hooks such as deinit or diagnostic naming. + * + * @return ePAR_OK on success, otherwise an implementation-defined package error. + */ +par_status_t par_store_flash_ee_native_port_init(void); + +/** + * @brief Deinitialize the user-supplied native flash port. + * + * @return ePAR_OK on success, otherwise an implementation-defined package error. + */ +par_status_t par_store_flash_ee_native_port_deinit(void); + +/** + * @brief Read raw bytes from the user-supplied native flash region. + * + * @param[in] addr Byte offset inside the bound persistence region. + * @param[in] size Number of bytes to read. + * @param[out] p_buf Destination buffer. + * @return ePAR_OK on success, otherwise an implementation-defined package error. + */ +par_status_t par_store_flash_ee_native_port_read(uint32_t addr, uint32_t size, uint8_t *p_buf); + +/** + * @brief Program raw bytes into the user-supplied native flash region. + * + * @param[in] addr Byte offset inside the bound persistence region. + * @param[in] size Number of bytes to program. + * @param[in] p_buf Source buffer. + * @return ePAR_OK on success, otherwise an implementation-defined package error. + */ +par_status_t par_store_flash_ee_native_port_program(uint32_t addr, uint32_t size, const uint8_t *p_buf); + +/** + * @brief Erase raw bytes inside the user-supplied native flash region. + * + * @param[in] addr Byte offset inside the bound persistence region. + * @param[in] size Number of bytes to erase. + * @return ePAR_OK on success, otherwise an implementation-defined package error. + */ +par_status_t par_store_flash_ee_native_port_erase(uint32_t addr, uint32_t size); + +/** + * @brief Return the total size of the user-supplied native flash region. + * + * @return Region size in bytes. + */ +uint32_t par_store_flash_ee_native_port_region_size(void); + +/** + * @brief Return the erase granularity of the user-supplied native flash region. + * + * @return Erase size in bytes. + */ +uint32_t par_store_flash_ee_native_port_erase_size(void); + +/** + * @brief Return the program granularity of the user-supplied native flash region. + * + * @return Program size in bytes. + */ +uint32_t par_store_flash_ee_native_port_program_size(void); + +/** + * @brief Return a short diagnostic name for the user-supplied native flash port. + * + * @return Null-terminated port name string. + */ +const char *par_store_flash_ee_native_port_name(void); +#endif /* (1 == PAR_CFG_NVM_BACKEND_FLASH_EE_PORT_NATIVE_EN) */ + +/** + * @brief Bind one physical flash port to the generic flash-emulated EEPROM core. + * + * @param[in] p_port_api Physical port API table. + * @param[in,out] p_port_ctx Mutable physical port context. + * @return ePAR_OK on success, otherwise a package error code. + */ +par_status_t par_store_backend_flash_ee_bind_port(const par_store_flash_ee_port_api_t *p_port_api, void *p_port_ctx); + +/** + * @brief Return the generic flash-emulated EEPROM backend API. + * + * @return Backend API pointer. + */ +const par_store_backend_api_t *par_store_backend_flash_ee_get_api(void); + +/** + * @brief Return the latest backend diagnostic code. + * + * @return Latest diagnostic code. + */ +par_store_flash_ee_diag_t par_store_backend_flash_ee_get_diag(void); + +/** + * @brief Return a human-readable string for one diagnostic code. + * + * @param[in] diag Diagnostic code. + * @return Constant string representation. + */ +const char *par_store_backend_flash_ee_get_diag_str(par_store_flash_ee_diag_t diag); + +#ifdef __cplusplus +} +#endif + +#endif /* (1 == PAR_CFG_NVM_EN) && (1 == PAR_CFG_NVM_BACKEND_FLASH_EE_EN) */ + +#endif /* PAR_STORE_BACKEND_FLASH_EE_H */ diff --git a/src/persist/backend/par_store_backend_flash_ee_cfg.h b/src/persist/backend/par_store_backend_flash_ee_cfg.h new file mode 100644 index 0000000..d87d383 --- /dev/null +++ b/src/persist/backend/par_store_backend_flash_ee_cfg.h @@ -0,0 +1,72 @@ +/** + * @file par_store_backend_flash_ee_cfg.h + * @brief Provide compile-time configuration defaults for the flash-ee backend. + * @author wdfk-prog + * @version 1.0 + * @date 2026-04-19 + * + * @copyright Copyright (c) 2026 Ziga Miklosic. Distributed under the MIT license. + * + * @note : + * @par Change Log: + * Date Version Author Description + * 2026-04-19 1.0 wdfk-prog first version + */ +#ifndef _PAR_STORE_BACKEND_FLASH_EE_CFG_H_ +#define _PAR_STORE_BACKEND_FLASH_EE_CFG_H_ + +#ifndef PAR_CFG_NVM_BACKEND_FLASH_EE_EN +#define PAR_CFG_NVM_BACKEND_FLASH_EE_EN (0) +#endif + +#ifndef PAR_CFG_NVM_BACKEND_FLASH_EE_LOGICAL_SIZE +#define PAR_CFG_NVM_BACKEND_FLASH_EE_LOGICAL_SIZE (4096u) +#endif + +#ifndef PAR_CFG_NVM_BACKEND_FLASH_EE_CACHE_SIZE +#define PAR_CFG_NVM_BACKEND_FLASH_EE_CACHE_SIZE (4096u) +#endif + +#ifndef PAR_CFG_NVM_BACKEND_FLASH_EE_LINE_SIZE +#define PAR_CFG_NVM_BACKEND_FLASH_EE_LINE_SIZE (32u) +#endif + +#ifndef PAR_CFG_NVM_BACKEND_FLASH_EE_PROGRAM_SIZE +#define PAR_CFG_NVM_BACKEND_FLASH_EE_PROGRAM_SIZE (8u) +#endif + +#ifndef PAR_CFG_NVM_BACKEND_FLASH_EE_PORT_FAL_EN +#define PAR_CFG_NVM_BACKEND_FLASH_EE_PORT_FAL_EN (0) +#endif + +#ifndef PAR_CFG_NVM_BACKEND_FLASH_EE_PORT_NATIVE_EN +#define PAR_CFG_NVM_BACKEND_FLASH_EE_PORT_NATIVE_EN (0) +#endif + +#ifndef PAR_CFG_NVM_BACKEND_FLASH_EE_FAL_PARTITION_NAME +#define PAR_CFG_NVM_BACKEND_FLASH_EE_FAL_PARTITION_NAME "autogen_pm" +#endif + +#ifndef PAR_CFG_NVM_BACKEND_FLASH_EE_VERSION +#define PAR_CFG_NVM_BACKEND_FLASH_EE_VERSION (1u) +#endif + +#if (1 == PAR_CFG_NVM_BACKEND_FLASH_EE_EN) +#if (0u == PAR_CFG_NVM_BACKEND_FLASH_EE_LINE_SIZE) +#error "Parameter settings invalid: flash-ee line size must be greater than 0!" +#elif (0u == PAR_CFG_NVM_BACKEND_FLASH_EE_LOGICAL_SIZE) +#error "Parameter settings invalid: flash-ee logical size must be greater than 0!" +#elif (0u != (PAR_CFG_NVM_BACKEND_FLASH_EE_LOGICAL_SIZE % PAR_CFG_NVM_BACKEND_FLASH_EE_LINE_SIZE)) +#error "Parameter settings invalid: flash-ee logical size must be an integer multiple of the line size!" +#elif (0u == PAR_CFG_NVM_BACKEND_FLASH_EE_CACHE_SIZE) +#error "Parameter settings invalid: flash-ee cache size must be greater than 0!" +#elif (0u != (PAR_CFG_NVM_BACKEND_FLASH_EE_CACHE_SIZE % PAR_CFG_NVM_BACKEND_FLASH_EE_LINE_SIZE)) +#error "Parameter settings invalid: flash-ee cache size must be an integer multiple of the line size!" +#elif (0u == PAR_CFG_NVM_BACKEND_FLASH_EE_PROGRAM_SIZE) +#error "Parameter settings invalid: flash-ee program size must be greater than 0!" +#elif (0u != (64u % PAR_CFG_NVM_BACKEND_FLASH_EE_PROGRAM_SIZE)) +#error "Parameter settings invalid: flash-ee program size must divide the 64-byte bank header exactly!" +#endif +#endif + +#endif /* _PAR_STORE_BACKEND_FLASH_EE_CFG_H_ */ diff --git a/src/persist/backend/par_store_backend_gel_nvm.c b/src/persist/backend/par_store_backend_gel_nvm.c new file mode 100644 index 0000000..2a63550 --- /dev/null +++ b/src/persist/backend/par_store_backend_gel_nvm.c @@ -0,0 +1,160 @@ +/** + * @file par_store_backend_gel_nvm.c + * @brief Adapt GeneralEmbeddedCLibraries/nvm to the packaged parameter-storage backend interface. + * @author wdfk-prog + * @version 1.0 + * @date 2026-03-29 + * + * @copyright Copyright (c) 2026 Ziga Miklosic. Distributed under the MIT license. + * + * @details This adapter is optional. Enable it only when the project includes + * the GeneralEmbeddedCLibraries/nvm module and a valid PAR_CFG_NVM_REGION is + * configured for parameter storage. + * @note : + * @par Change Log: + * Date Version Author Description + * 2026-03-29 1.0 wdfk-prog first version + */ +#include "par_cfg.h" +#include "persist/backend/par_store_backend.h" + +#if (1 == PAR_CFG_NVM_EN) && (1 == PAR_CFG_NVM_BACKEND_GEL_EN) +#include "middleware/nvm/nvm/src/nvm.h" + +/** + * @brief Verify the expected upstream NVM major/minor API. + */ +_Static_assert(2 == NVM_VER_MAJOR); +_Static_assert(1 <= NVM_VER_MINOR); + +/** + * @brief Initialize the GeneralEmbeddedCLibraries NVM backend. + * + * @return ePAR_OK on success, otherwise an initialization error. + */ +static par_status_t par_store_gel_init(void) +{ + return (eNVM_OK == nvm_init()) ? ePAR_OK : ePAR_ERROR_INIT; +} + +/** + * @brief Deinitialize the GeneralEmbeddedCLibraries NVM backend. + * + * @return ePAR_OK on success, otherwise a backend error. + */ +static par_status_t par_store_gel_deinit(void) +{ + return (eNVM_OK == nvm_deinit()) ? ePAR_OK : ePAR_ERROR; +} + +/** + * @brief Report whether the GeneralEmbeddedCLibraries NVM backend is initialized. + * + * @param[out] p_is_init Receives the initialization state. + * @return ePAR_OK on success, otherwise an error code. + */ +static par_status_t par_store_gel_is_init(bool * const p_is_init) +{ + if (NULL == p_is_init) + { + return ePAR_ERROR_PARAM; + } + + return (eNVM_OK == nvm_is_init(p_is_init)) ? ePAR_OK : ePAR_ERROR_INIT; +} + +/** + * @brief Read raw bytes from the configured NVM region. + * + * @param[in] addr Byte offset inside the parameter NVM region. + * @param[in] size Number of bytes to read. + * @param[out] p_buf Destination buffer. + * @return ePAR_OK on success, otherwise an NVM error. + */ +static par_status_t par_store_gel_read(const uint32_t addr, const uint32_t size, uint8_t * const p_buf) +{ + if (NULL == p_buf) + { + return ePAR_ERROR_PARAM; + } + + return (eNVM_OK == nvm_read(PAR_CFG_NVM_REGION, addr, size, p_buf)) ? ePAR_OK : ePAR_ERROR_NVM; +} + +/** + * @brief Write raw bytes into the configured NVM region. + * + * @param[in] addr Byte offset inside the parameter NVM region. + * @param[in] size Number of bytes to write. + * @param[in] p_buf Source buffer. + * @return ePAR_OK on success, otherwise an NVM error. + */ +static par_status_t par_store_gel_write(const uint32_t addr, const uint32_t size, const uint8_t * const p_buf) +{ + if (NULL == p_buf) + { + return ePAR_ERROR_PARAM; + } + + return (eNVM_OK == nvm_write(PAR_CFG_NVM_REGION, addr, size, p_buf)) ? ePAR_OK : ePAR_ERROR_NVM; +} + +/** + * @brief Erase bytes inside the configured NVM region. + * + * @param[in] addr Byte offset inside the parameter NVM region. + * @param[in] size Number of bytes to erase. + * @return ePAR_OK on success, otherwise an NVM error. + */ +static par_status_t par_store_gel_erase(const uint32_t addr, const uint32_t size) +{ + return (eNVM_OK == nvm_erase(PAR_CFG_NVM_REGION, addr, size)) ? ePAR_OK : ePAR_ERROR_NVM; +} + +/** + * @brief Flush pending writes inside the configured NVM region. + * + * @return ePAR_OK on success, otherwise an NVM error. + */ +static par_status_t par_store_gel_sync(void) +{ + return (eNVM_OK == nvm_sync(PAR_CFG_NVM_REGION)) ? ePAR_OK : ePAR_ERROR_NVM; +} + +/** + * @brief Prepare the GeneralEmbeddedCLibraries adapter before API resolution. + * + * @details This backend has no extra bind step, so the function is a no-op. + * + * @return ePAR_OK. + */ +par_status_t par_store_backend_bind(void) +{ + return ePAR_OK; +} + +/** + * @brief Concrete GeneralEmbeddedCLibraries NVM backend API table. + */ +static const par_store_backend_api_t g_par_store_backend_gel = +{ + .init = par_store_gel_init, + .deinit = par_store_gel_deinit, + .is_init = par_store_gel_is_init, + .read = par_store_gel_read, + .write = par_store_gel_write, + .erase = par_store_gel_erase, + .sync = par_store_gel_sync, + .name = "gel_nvm", +}; + +/** + * @brief Return the GeneralEmbeddedCLibraries NVM backend API. + * + * @return Backend API table. + */ +const par_store_backend_api_t * par_store_backend_get_api(void) +{ + return &g_par_store_backend_gel; +} +#endif /* (1 == PAR_CFG_NVM_EN) && (1 == PAR_CFG_NVM_BACKEND_GEL_EN) */ diff --git a/src/persist/fnv.h b/src/persist/fnv.h new file mode 100644 index 0000000..6d4bc2a --- /dev/null +++ b/src/persist/fnv.h @@ -0,0 +1,28 @@ +/** + * @file fnv.h + * @brief Declare the bundled 32-bit FNV-1a hash helpers used by table-ID support. + * @author wdfk-prog + * @version 1.0 + * @date 2026-04-19 + * + * @copyright Copyright (c) 2026 Ziga Miklosic. Distributed under the MIT license. + * + * @note : + * @par Change Log: + * Date Version Author Description + * 2026-04-19 1.0 wdfk-prog first version + */ +#ifndef _PAR_PERSIST_FNV_H_ +#define _PAR_PERSIST_FNV_H_ + +#include +#include + +typedef uint32_t Fnv32_t; + +#define FNV1_32A_INIT ((Fnv32_t)0x811C9DC5U) + +Fnv32_t fnv_32a_buf(void *buf, size_t len, Fnv32_t hval); +Fnv32_t fnv_32a_str(char *str, Fnv32_t hval); + +#endif /* _PAR_PERSIST_FNV_H_ */ diff --git a/src/persist/hash_32a.c b/src/persist/hash_32a.c new file mode 100644 index 0000000..746279a --- /dev/null +++ b/src/persist/hash_32a.c @@ -0,0 +1,177 @@ +/** + * @file hash_32a.c + * @brief Provide bundled 32-bit FNV-1a hash support for table-ID calculation. + * @author Landon Curt Noll + * @version 1.0 + * @date 2026-04-19 + * + * @copyright Public-domain upstream FNV source bundled with this repository. + * + * @note : + * @par Change Log: + * Date Version Author Description + * 2026-04-19 1.0 wdfk-prog add project file header for bundled FNV source + */ + +/* + * hash_32 - 32 bit Fowler/Noll/Vo FNV-1a hash code + * + *** + * + * For the most up to date copy of this code, see: + * + * https://github.com/lcn2/fnv + * + * For more information on the FNV hash, see: + * + * http://www.isthe.com/chongo/tech/comp/fnv/index.html + * + *** + * + * Fowler/Noll/Vo hash + * + * The basis of this hash algorithm was taken from an idea sent + * as reviewer comments to the IEEE POSIX P1003.2 committee by: + * + * Phong Vo (http://www.research.att.com/info/kpv/) + * Glenn Fowler (http://www.research.att.com/~gsf/) + * + * In a subsequent ballot round: + * + * Landon Curt Noll (http://www.isthe.com/chongo/) + * + * improved on their algorithm. Some people tried this hash + * and found that it worked rather well. In an EMail message + * to Landon, they named it the ``Fowler/Noll/Vo'' or FNV hash. + * + * FNV hashes are designed to be fast while maintaining a low + * collision rate. The FNV speed allows one to quickly hash lots + * of data while maintaining a reasonable collision rate. + * + *** + * + * This is free and unencumbered software released into the public domain. + * + * Anyone is free to copy, modify, publish, use, compile, sell, or + * distribute this software, either in source code form or as a compiled + * binary, for any purpose, commercial or non-commercial, and by any + * means. + * + * In jurisdictions that recognize copyright laws, the author or authors + * of this software dedicate any and all copyright interest in the + * software to the public domain. We make this dedication for the benefit + * of the public at large and to the detriment of our heirs and + * successors. We intend this dedication to be an overt act of + * relinquishment in perpetuity of all present and future rights to this + * software under copyright law. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * For more information, please refer to + * + *** + * + * Author: + * + * chongo (Landon Curt Noll) /\oo/\ + * + * http://www.isthe.com/chongo/index.html + * https://github.com/lcn2 + * + * Share and enjoy! :-) + */ + +#include +#include "fnv.h" + + +/* + * 32 bit magic FNV-1a prime + */ +#define FNV_32_PRIME ((Fnv32_t)0x01000193) + + +/* + * fnv_32a_buf - perform a 32 bit Fowler/Noll/Vo FNV-1a hash on a buffer + * + * input: + * buf - start of buffer to hash + * len - length of buffer in octets + * hval - previous hash value or 0 if first call + * + * returns: + * 32 bit hash as a static hash type + * + * NOTE: To use the recommended 32 bit FNV-1a hash, use FNV1_32A_INIT as the + * hval arg on the first call to either fnv_32a_buf() or fnv_32a_str(). + */ +Fnv32_t +fnv_32a_buf(void *buf, size_t len, Fnv32_t hval) +{ + unsigned char *bp = (unsigned char *)buf; /* start of buffer */ + unsigned char *be = bp + len; /* beyond end of buffer */ + + /* + * FNV-1a hash each octet in the buffer + */ + while (bp < be) { + + /* xor the bottom with the current octet */ + hval ^= (Fnv32_t)*bp++; + + /* multiply by the 32 bit FNV magic prime mod 2^32 */ +#if defined(NO_FNV_GCC_OPTIMIZATION) + hval *= FNV_32_PRIME; +#else + hval += (hval<<1) + (hval<<4) + (hval<<7) + (hval<<8) + (hval<<24); +#endif + } + + /* return our new hash value */ + return hval; +} + + +/* + * fnv_32a_str - perform a 32 bit Fowler/Noll/Vo FNV-1a hash on a string + * + * input: + * str - string to hash + * hval - previous hash value or 0 if first call + * + * returns: + * 32 bit hash as a static hash type + * + * NOTE: To use the recommended 32 bit FNV-1a hash, use FNV1_32A_INIT as the + * hval arg on the first call to either fnv_32a_buf() or fnv_32a_str(). + */ +Fnv32_t +fnv_32a_str(char *str, Fnv32_t hval) +{ + unsigned char *s = (unsigned char *)str; /* unsigned string */ + + /* + * FNV-1a hash each octet in the buffer + */ + while (*s) { + + /* xor the bottom with the current octet */ + hval ^= (Fnv32_t)*s++; + + /* multiply by the 32 bit FNV magic prime mod 2^32 */ +#if defined(NO_FNV_GCC_OPTIMIZATION) + hval *= FNV_32_PRIME; +#else + hval += (hval<<1) + (hval<<4) + (hval<<7) + (hval<<8) + (hval<<24); +#endif + } + + /* return our new hash value */ + return hval; +} \ No newline at end of file diff --git a/src/persist/par_nvm.c b/src/persist/par_nvm.c new file mode 100644 index 0000000..051483a --- /dev/null +++ b/src/persist/par_nvm.c @@ -0,0 +1,1456 @@ +/** + * @file par_nvm.c + * @brief Implement non-volatile storage support for parameters. + * @author Ziga Miklosic + * @version V3.0.1 + * @date 2026-01-29 + * + * @copyright Copyright (c) 2026 Ziga Miklosic. Distributed under the MIT license. + * + * @note : + * @par Change Log: + * Date Version Author Description + * 2026-03-30 1.2 wdfk-prog simplify NVM flow and table-ID handling + */ +/** + * @addtogroup PAR_NVM + * @{ + * + * Parameter storage to non-volatile memory handling. + * + * + * @pre NVM module shall have memory region called "Parameters". + * + * NVM module may already be initialized by the application, or it. + * may be initialized on demand by this module when needed. + * + * @brief This module is responsible for parameter NVM object creation and. + * storage manipulation. NVM parameter object consist of it's value. + * and a CRC value for validation purposes. + * + * Parameter storage is reserved in "Parameters" region of NVM. Look. + * at the nvm_cfg.h/c module for NVM region descriptions. + * + * Parameters stored into NVM in the native byte order of the target platform. + * + * For details how parameters are handled in NVM go look at the. + * documentation. + * + * @note RULES OF "PAR_CFG_TABLE_ID_CHECK_EN" SETTINGS: + * + * During development it is normal that the parameter table and the persisted + * compatibility model evolve. The serialized NVM header therefore stores the + * current table-ID digest together with the persistent-object count, and the + * header CRC protects both fields. Startup validates the live table-ID against + * the stored digest when table-ID checking is enabled. + * + * A mismatch means the managed NVM image is no longer compatible with the live + * firmware image. Typical causes are an intentional schema-version bump, + * stored-prefix layout drift, or stored-image corruption. Self-describing + * layouts treat persisted-parameter ID changes as incompatible. The fixed + * payload-only layout intentionally allows pure external-ID renumbering and + * compatible tail growth, while the grouped payload-only layout rebuilds on + * any stored/live count mismatch. + * The recovery path is centralized in par_nvm_init(): restore defaults first, + * then rebuild the managed NVM image only for errors that require a rewrite. + */ +/** + * @brief Include dependencies. + */ +#include "persist/par_nvm.h" +#include "par_cfg.h" +#include "port/par_if.h" + +#if (1 == PAR_CFG_NVM_EN) + +#include +#include +#include + +#include "persist/backend/par_store_backend.h" +#include "persist/par_nvm_layout.h" +#include "persist/par_nvm_table_id.h" +/** + * @brief Compile-time definitions. + */ +/** + * @brief Parameter signature value. + */ +#define PAR_NVM_SIGN (0xFF00AA55) + +/** + * @brief Parameter NVM header content address start. + * + * @note This is offset to reserved NVM region. For absolute address. + * add that value to NVM start region. + */ +#define PAR_NVM_HEAD_ADDR (0x00U) +#define PAR_NVM_HEAD_SIGN_ADDR (PAR_NVM_HEAD_ADDR) +#define PAR_NVM_HEAD_SIGN_SIZE ((uint32_t)sizeof(uint32_t)) +#define PAR_NVM_HEAD_OBJ_NB_OFFSET (PAR_NVM_HEAD_SIGN_SIZE) +#define PAR_NVM_HEAD_OBJ_NB_SIZE ((uint32_t)sizeof(uint16_t)) +#define PAR_NVM_HEAD_TABLE_ID_OFFSET (PAR_NVM_HEAD_OBJ_NB_OFFSET + PAR_NVM_HEAD_OBJ_NB_SIZE) +#define PAR_NVM_HEAD_TABLE_ID_SIZE ((uint32_t)sizeof(uint32_t)) +#define PAR_NVM_HEAD_CRC_OFFSET (PAR_NVM_HEAD_TABLE_ID_OFFSET + PAR_NVM_HEAD_TABLE_ID_SIZE) +#define PAR_NVM_HEAD_CRC_SIZE ((uint32_t)sizeof(uint16_t)) +#define PAR_NVM_HEAD_SIZE (PAR_NVM_HEAD_CRC_OFFSET + PAR_NVM_HEAD_CRC_SIZE) +#define PAR_NVM_FIRST_DATA_OBJ_ADDR (PAR_NVM_HEAD_ADDR + PAR_NVM_HEAD_SIZE) + +/** + * @brief Runtime persistence-slot state. + * + * @details The common NVM flow uses this structure to track which compiled + * persistent slots were reconstructed successfully from the serialized image. + */ +typedef struct +{ + bool loaded_slots[PAR_PERSIST_SLOT_MAP_CAPACITY]; /**< Runtime-loaded flag for each compiled persistent slot. */ + uint16_t loaded_count; /**< Number of runtime-loaded persistent slots. */ +} par_nvm_slot_runtime_t; + +#if (1 == PAR_CFG_ENABLE_NAME) +#define PAR_NVM_DBG_NAME_ARG(cfg_) (((const par_cfg_t *)(cfg_) != NULL) && (((const par_cfg_t *)(cfg_))->name != NULL) ? ((const par_cfg_t *)(cfg_))->name : "") +#else +#define PAR_NVM_DBG_NAME_ARG(cfg_) "" +#endif + +#if (1 == PAR_CFG_ENABLE_ID) +#define PAR_NVM_CFG_ID_VALUE(cfg_) (((const par_cfg_t *)(cfg_)) != NULL ? ((const par_cfg_t *)(cfg_))->id : 0U) +#else +#define PAR_NVM_CFG_ID_VALUE(cfg_) (0U) +#endif + +#define PAR_PERSIST_SLOT_ENTRY_SELECT(enum_, pers_) PAR_PERSIST_SLOT_ENTRY_SELECT_I(enum_, pers_) +#define PAR_PERSIST_SLOT_ENTRY_SELECT_I(enum_, pers_) PAR_PERSIST_SLOT_ENTRY_SELECT_##pers_(enum_) +#define PAR_PERSIST_SLOT_ENTRY_SELECT_true(enum_) [PAR_PERSIST_IDX_##enum_] = enum_, +#define PAR_PERSIST_SLOT_ENTRY_SELECT_false(enum_) +#define PAR_PERSIST_SLOT_ENTRY_SELECT_1(enum_) [PAR_PERSIST_IDX_##enum_] = enum_, +#define PAR_PERSIST_SLOT_ENTRY_SELECT_0(enum_) +#define PAR_ITEM_PERSIST_SLOT(...) PAR_PERSIST_SLOT_ENTRY_SELECT(PAR_XARG_ENUM(__VA_ARGS__), PAR_XARG_PERS(__VA_ARGS__)) +/** + * @brief Compile-time mapping from persistent slot to live parameter number. + * + * @details This table is derived directly from par_table.def. Slot order is the + * single source of truth for the managed NVM payload layout used by par_nvm.c. + */ +static const par_num_t g_par_persist_slot_to_par_num[PAR_PERSIST_SLOT_MAP_CAPACITY] = { +#define PAR_ITEM_U8 PAR_ITEM_PERSIST_SLOT +#define PAR_ITEM_U16 PAR_ITEM_PERSIST_SLOT +#define PAR_ITEM_U32 PAR_ITEM_PERSIST_SLOT +#define PAR_ITEM_I8 PAR_ITEM_PERSIST_SLOT +#define PAR_ITEM_I16 PAR_ITEM_PERSIST_SLOT +#define PAR_ITEM_I32 PAR_ITEM_PERSIST_SLOT +#define PAR_ITEM_F32 PAR_ITEM_PERSIST_SLOT +#include "../../par_table.def" +#undef PAR_ITEM_U8 +#undef PAR_ITEM_U16 +#undef PAR_ITEM_U32 +#undef PAR_ITEM_I8 +#undef PAR_ITEM_I16 +#undef PAR_ITEM_I32 +#undef PAR_ITEM_F32 +}; +#undef PAR_ITEM_PERSIST_SLOT +#undef PAR_PERSIST_SLOT_ENTRY_SELECT +#undef PAR_PERSIST_SLOT_ENTRY_SELECT_I +#undef PAR_PERSIST_SLOT_ENTRY_SELECT_true +#undef PAR_PERSIST_SLOT_ENTRY_SELECT_false +#undef PAR_PERSIST_SLOT_ENTRY_SELECT_1 +#undef PAR_PERSIST_SLOT_ENTRY_SELECT_0 + +/** + * @brief Module-scope variables. + */ +/** + * @brief Initialization guard. + */ +static bool gb_is_init = false; +/** + * @brief Ownership guard for the mounted storage backend. + */ +static bool gb_is_nvm_owner = false; +/** + * @brief Selected parameter storage backend API. + * + * @details The backend is resolved once during initialization. The core NVM + * logic then uses only this abstract interface and no longer depends on a + * specific repository layout. + */ +static const par_store_backend_api_t *gp_store = NULL; +/** + * @brief Selected persisted-record layout adapter. + */ +static const par_nvm_layout_api_t *gp_layout = NULL; +/** + * @brief Runtime state of compiled persistent slots. + */ +static par_nvm_slot_runtime_t g_par_nvm_slot_runtime = { 0 }; + +/** + * @brief Resolve natural payload size from parameter type. + * + * @param type Parameter type. + * @return Natural payload width in bytes. + */ +uint8_t par_nvm_layout_payload_size_from_type(const par_type_list_t type) +{ + switch (type) + { + case ePAR_TYPE_U8: + case ePAR_TYPE_I8: + return 1U; + + case ePAR_TYPE_U16: + case ePAR_TYPE_I16: + return 2U; + + case ePAR_TYPE_U32: + case ePAR_TYPE_I32: +#if (1 == PAR_CFG_ENABLE_TYPE_F32) + case ePAR_TYPE_F32: +#endif + return 4U; + + case ePAR_TYPE_NUM_OF: + default: + PAR_ASSERT(0); + return PAR_NVM_RECORD_DATA_SLOT_SIZE; + } +} + +/** + * @brief Resolve one persistent slot payload width. + * + * @param par_num Live parameter number. + * @return Natural payload width in bytes. + */ +uint8_t par_nvm_layout_payload_size_from_par_num(const par_num_t par_num) +{ + const par_cfg_t * const p_cfg = par_get_config(par_num); + + PAR_ASSERT(NULL != p_cfg); + return par_nvm_layout_payload_size_from_type(p_cfg->type); +} + +/** + * @brief Serialize one parameter value to native-endian payload bytes. + * + * @param type Parameter type. + * @param p_data Canonical parameter value. + * @param p_payload Output payload buffer. + */ +void par_nvm_layout_pack_payload_bytes(const par_type_list_t type, + const par_type_t * const p_data, + uint8_t * const p_payload) +{ + PAR_ASSERT((NULL != p_data) && (NULL != p_payload)); + + switch (type) + { + case ePAR_TYPE_U8: + { + const uint8_t value = p_data->u8; + memcpy(p_payload, &value, sizeof(value)); + break; + } + + case ePAR_TYPE_I8: + { + const int8_t value = p_data->i8; + memcpy(p_payload, &value, sizeof(value)); + break; + } + + case ePAR_TYPE_U16: + { + const uint16_t value = p_data->u16; + memcpy(p_payload, &value, sizeof(value)); + break; + } + + case ePAR_TYPE_I16: + { + const int16_t value = p_data->i16; + memcpy(p_payload, &value, sizeof(value)); + break; + } + + case ePAR_TYPE_U32: + { + const uint32_t value = p_data->u32; + memcpy(p_payload, &value, sizeof(value)); + break; + } + + case ePAR_TYPE_I32: + { + const int32_t value = p_data->i32; + memcpy(p_payload, &value, sizeof(value)); + break; + } + +#if (1 == PAR_CFG_ENABLE_TYPE_F32) + case ePAR_TYPE_F32: + { + const float32_t value = p_data->f32; + memcpy(p_payload, &value, sizeof(value)); + break; + } +#endif + + case ePAR_TYPE_NUM_OF: + default: + PAR_ASSERT(0); + break; + } +} + +/** + * @brief Deserialize native-endian payload bytes into the canonical value carrier. + * + * @param type Parameter type. + * @param p_payload Input payload buffer. + * @param p_data Output canonical parameter value. + */ +void par_nvm_layout_unpack_payload_bytes(const par_type_list_t type, + const uint8_t * const p_payload, + par_type_t * const p_data) +{ + PAR_ASSERT((NULL != p_payload) && (NULL != p_data)); + + switch (type) + { + case ePAR_TYPE_U8: + { + uint8_t value = 0U; + memcpy(&value, p_payload, sizeof(value)); + p_data->u8 = value; + break; + } + + case ePAR_TYPE_I8: + { + int8_t value = 0; + memcpy(&value, p_payload, sizeof(value)); + p_data->i8 = value; + break; + } + + case ePAR_TYPE_U16: + { + uint16_t value = 0U; + memcpy(&value, p_payload, sizeof(value)); + p_data->u16 = value; + break; + } + + case ePAR_TYPE_I16: + { + int16_t value = 0; + memcpy(&value, p_payload, sizeof(value)); + p_data->i16 = value; + break; + } + + case ePAR_TYPE_U32: + { + uint32_t value = 0U; + memcpy(&value, p_payload, sizeof(value)); + p_data->u32 = value; + break; + } + + case ePAR_TYPE_I32: + { + int32_t value = 0; + memcpy(&value, p_payload, sizeof(value)); + p_data->i32 = value; + break; + } + +#if (1 == PAR_CFG_ENABLE_TYPE_F32) + case ePAR_TYPE_F32: + { + float32_t value = 0.0f; + memcpy(&value, p_payload, sizeof(value)); + p_data->f32 = value; + break; + } +#endif + + case ePAR_TYPE_NUM_OF: + default: + PAR_ASSERT(0); + break; + } +} + +/** + * @brief Calculate per-record CRC-8 over serialized record bytes without an ID field. + * + * @param size_desc Serialized size descriptor. + * @param p_payload Pointer to payload bytes. + * @param payload_size Number of payload bytes. + * @param include_size_desc True when the layout includes a size field. + * @return Calculated CRC-8 value. + */ +uint8_t par_nvm_layout_calc_crc(const uint8_t size_desc, + const uint8_t * const p_payload, + const uint8_t payload_size, + const bool include_size_desc) +{ + uint8_t crc = PAR_IF_CRC8_INIT; + + PAR_ASSERT(NULL != p_payload); + + if (include_size_desc) + { + crc = par_if_crc8_accumulate(crc, (const uint8_t * const)&size_desc, (uint32_t)sizeof(size_desc)); + } + crc = par_if_crc8_accumulate(crc, p_payload, (uint32_t)payload_size); + + return crc; +} + +/** + * @brief Calculate per-record CRC-8 over serialized record bytes with an ID field. + * + * @param id Parameter ID. + * @param size_desc Serialized size descriptor. + * @param p_payload Pointer to payload bytes. + * @param payload_size Number of payload bytes. + * @param include_size_desc True when the layout includes a size field. + * @return Calculated CRC-8 value. + */ +uint8_t par_nvm_layout_calc_crc_with_id(const uint16_t id, + const uint8_t size_desc, + const uint8_t * const p_payload, + const uint8_t payload_size, + const bool include_size_desc) +{ + uint8_t crc = PAR_IF_CRC8_INIT; + + PAR_ASSERT(NULL != p_payload); + + crc = par_if_crc8_accumulate(crc, (const uint8_t * const)&id, (uint32_t)sizeof(id)); + if (include_size_desc) + { + crc = par_if_crc8_accumulate(crc, (const uint8_t * const)&size_desc, (uint32_t)sizeof(size_desc)); + } + crc = par_if_crc8_accumulate(crc, p_payload, (uint32_t)payload_size); + + return crc; +} + +/** + * @brief Calculate serialized-header CRC-16. + * + * @details The header CRC is accumulated field-by-field over the serialized + * native-order bytes of obj_nb and table_id. This avoids hashing compiler + * padding bytes inside par_nvm_head_obj_t while intentionally keeping the + * persisted format and table-ID comparison tied to the current target + * architecture. + * + * @param p_head_obj Pointer to header object. + * @return Calculated CRC-16 value. + */ +static uint16_t par_nvm_calc_head_crc(const par_nvm_head_obj_t * const p_head_obj) +{ + uint16_t crc = PAR_IF_CRC16_INIT; + + PAR_ASSERT(NULL != p_head_obj); + + crc = par_if_crc16_accumulate(crc, (const uint8_t * const)&p_head_obj->obj_nb, (uint32_t)sizeof(p_head_obj->obj_nb)); + crc = par_if_crc16_accumulate(crc, (const uint8_t * const)&p_head_obj->table_id, (uint32_t)sizeof(p_head_obj->table_id)); + + return crc; +} +/** + * @brief Read parameter NVM header. + * + * @param p_head_obj Pointer to parameter NVM header. + * @return Status of operation. + */ +static par_status_t par_nvm_read_header(par_nvm_head_obj_t * const p_head_obj) +{ + par_status_t status = ePAR_OK; + uint8_t head_buf[PAR_NVM_HEAD_SIZE] = { 0U }; + + PAR_ASSERT(NULL != p_head_obj); + + const par_status_t store_status = gp_store->read(PAR_NVM_HEAD_ADDR, PAR_NVM_HEAD_SIZE, head_buf); + if (ePAR_OK != store_status) + { + status = ePAR_ERROR_NVM; + PAR_ERR_PRINT("PAR_NVM: header read failed, err=%u", (unsigned)store_status); + } + else + { + memcpy(&p_head_obj->sign, &head_buf[PAR_NVM_HEAD_SIGN_ADDR], sizeof(p_head_obj->sign)); + memcpy(&p_head_obj->obj_nb, &head_buf[PAR_NVM_HEAD_OBJ_NB_OFFSET], sizeof(p_head_obj->obj_nb)); + memcpy(&p_head_obj->table_id, &head_buf[PAR_NVM_HEAD_TABLE_ID_OFFSET], sizeof(p_head_obj->table_id)); + memcpy(&p_head_obj->crc, &head_buf[PAR_NVM_HEAD_CRC_OFFSET], sizeof(p_head_obj->crc)); + } + + return status; +} + +/** + * @brief Flush pending backend data and normalize sync failures. + * + * @param p_context Short log context for diagnostics. + * @return Operation status. + */ +static par_status_t par_nvm_sync_backend(const char * const p_context) +{ + const par_status_t sync_status = gp_store->sync(); + + if (ePAR_OK != sync_status) + { + PAR_ERR_PRINT("PAR_NVM: sync failed%s%s err=%u", + (NULL != p_context) ? " " : "", + (NULL != p_context) ? p_context : "", + (unsigned)sync_status); + return ePAR_ERROR_NVM; + } + + return ePAR_OK; +} + +#if (1 == PAR_CFG_NVM_WRITE_VERIFY_EN) +/** + * @brief Verify a freshly written header by reading it back. + * + * @param p_expected Expected serialized header fields. + * @return Operation status. + */ +static par_status_t par_nvm_verify_header_readback(const par_nvm_head_obj_t * const p_expected) +{ + par_nvm_head_obj_t readback = { 0 }; + par_status_t status = ePAR_OK; + + PAR_ASSERT(NULL != p_expected); + + status = par_nvm_read_header(&readback); + if (ePAR_OK != status) + { + return status; + } + + if ((readback.sign != p_expected->sign) || + (readback.obj_nb != p_expected->obj_nb) || + (readback.table_id != p_expected->table_id) || + (readback.crc != p_expected->crc)) + { + PAR_ERR_PRINT("PAR_NVM: header readback mismatch exp(sign=0x%08lX,obj=%u,table=0x%08lX,crc=0x%04X) got(sign=0x%08lX,obj=%u,table=0x%08lX,crc=0x%04X)", + (unsigned long)p_expected->sign, + (unsigned)p_expected->obj_nb, + (unsigned long)p_expected->table_id, + (unsigned)p_expected->crc, + (unsigned long)readback.sign, + (unsigned)readback.obj_nb, + (unsigned long)readback.table_id, + (unsigned)readback.crc); + return ePAR_ERROR_NVM; + } + + if (par_nvm_calc_head_crc(&readback) != readback.crc) + { + PAR_ERR_PRINT("PAR_NVM: header readback CRC validation failed"); + return (par_status_t)(ePAR_ERROR_NVM | ePAR_ERROR_CRC); + } + + return ePAR_OK; +} + +/** + * @brief Verify one freshly written persisted record by reading it back. + * + * @param addr Record start address. + * @param par_num Live parameter number. + * @param p_expected Expected canonical object. + * @return Operation status. + */ +static par_status_t par_nvm_verify_record_readback(const uint32_t addr, + const par_num_t par_num, + const par_nvm_data_obj_t * const p_expected) +{ + par_nvm_data_obj_t readback = { 0 }; + par_status_t status = ePAR_OK; + + PAR_ASSERT(NULL != p_expected); + + status = gp_layout->read(gp_store, addr, par_num, &readback); + if (ePAR_OK != status) + { + PAR_ERR_PRINT("PAR_NVM: record readback failed, par_num=%u addr=0x%08lX err=%u", + (unsigned)par_num, + (unsigned long)addr, + (unsigned)status); + return (par_status_t)(ePAR_ERROR_NVM | (status & ePAR_ERROR_CRC)); + } + + if (false == gp_layout->data_obj_matches(par_num, p_expected, &readback)) + { + PAR_ERR_PRINT("PAR_NVM: record readback mismatch, par_num=%u addr=0x%08lX", + (unsigned)par_num, + (unsigned long)addr); + return ePAR_ERROR_NVM; + } + + return ePAR_OK; +} +#endif /* 1 == PAR_CFG_NVM_WRITE_VERIFY_EN */ +/** + * @brief Write parameter NVM header. + * + * @details The serialized header always stores the current table-ID digest + * in the native byte order of the running target. + * The compatibility comparison against the live digest still runs only when + * table-ID checking is enabled. + * + * @param num_of_par Number of persistent parameters that are stored in NVM. + * @return Status of operation. + */ +static par_status_t par_nvm_write_header(const uint16_t num_of_par) +{ + par_status_t status = ePAR_OK; + par_nvm_head_obj_t head_obj = { 0 }; + uint8_t head_buf[PAR_NVM_HEAD_SIZE] = { 0U }; + + head_obj.obj_nb = num_of_par; + head_obj.table_id = par_nvm_table_id_calc_for_count(num_of_par); + head_obj.crc = par_nvm_calc_head_crc(&head_obj); + head_obj.sign = PAR_NVM_SIGN; + + memcpy(&head_buf[PAR_NVM_HEAD_SIGN_ADDR], &head_obj.sign, sizeof(head_obj.sign)); + memcpy(&head_buf[PAR_NVM_HEAD_OBJ_NB_OFFSET], &head_obj.obj_nb, sizeof(head_obj.obj_nb)); + memcpy(&head_buf[PAR_NVM_HEAD_TABLE_ID_OFFSET], &head_obj.table_id, sizeof(head_obj.table_id)); + memcpy(&head_buf[PAR_NVM_HEAD_CRC_OFFSET], &head_obj.crc, sizeof(head_obj.crc)); + + const par_status_t store_status = gp_store->write(PAR_NVM_HEAD_ADDR, PAR_NVM_HEAD_SIZE, head_buf); + if (ePAR_OK != store_status) + { + status = ePAR_ERROR_NVM; + PAR_ERR_PRINT("PAR_NVM: header write failed, err=%u", (unsigned)store_status); + return status; + } + + status = par_nvm_sync_backend("after header write"); + if (ePAR_OK != status) + { + return status; + } + +#if (1 == PAR_CFG_NVM_WRITE_VERIFY_EN) + status = par_nvm_verify_header_readback(&head_obj); + if (ePAR_OK != status) + { + PAR_ERR_PRINT("PAR_NVM: header readback verification failed, err=%u", (unsigned)status); + return status; + } +#endif + + PAR_DBG_PRINT("PAR_NVM: writing header with obj_count=%d", num_of_par); + + return status; +} +/** + * @brief Validate parameter NVM header. + * + * @details The header CRC covers both the stored persistent-object count and + * the stored table-ID digest bytes. This distinguishes header corruption from + * a valid-but-incompatible table-ID mismatch. + * + * @param p_head_obj Pointer to validated header structure. + * @return Status of operation. + */ +static par_status_t par_nvm_validate_header(par_nvm_head_obj_t * const p_head_obj) +{ + par_status_t status = ePAR_OK; + uint16_t crc_calc = 0U; + + status = par_nvm_read_header(p_head_obj); + if (ePAR_ERROR_NVM != status) + { + if (PAR_NVM_SIGN == p_head_obj->sign) + { + crc_calc = par_nvm_calc_head_crc(p_head_obj); + if (crc_calc == p_head_obj->crc) + { + PAR_DBG_PRINT("PAR_NVM: header validated, stored_obj_count=%d", p_head_obj->obj_nb); + } + else + { + status = ePAR_ERROR_CRC; + PAR_WARN_PRINT("PAR_NVM: header CRC corrupted"); + } + } + else + { + status = ePAR_ERROR; + PAR_WARN_PRINT("PAR_NVM: header signature corrupted"); + } + } + + return status; +} + +/** + * @brief Resolve a persistent slot index to a live parameter number. + * + * @param persist_idx Persistent slot index. + * @param p_par_num Output live parameter number. + * @return Operation status. + */ +static par_status_t par_nvm_get_num_by_persist_idx(const uint16_t persist_idx, par_num_t * const p_par_num) +{ + if ((persist_idx >= PAR_PERSISTENT_COMPILE_COUNT) || (NULL == p_par_num)) + { + return ePAR_ERROR; + } + + *p_par_num = g_par_persist_slot_to_par_num[persist_idx]; + return ePAR_OK; +} +/** + * @brief Convert a compile-time persistent slot index into the managed NVM object address. + * + * @details The public NVM flow keeps this common entry point, while the active + * layout implementation owns the actual address calculation. + * + * @param persist_idx Persistent slot index. + * @return Start address of the slot inside the managed NVM payload area. + */ +static uint32_t par_nvm_addr_from_persist_idx(const uint16_t persist_idx) +{ + return gp_layout->addr_from_persist_idx(PAR_NVM_FIRST_DATA_OBJ_ADDR, persist_idx, g_par_persist_slot_to_par_num); +} + +/** + * @brief Print the compiled persistent-slot map and current runtime load state. + * + * This function is intended for debug use only. It prints, for each compiled + * persistent slot, the slot index, parameter ID, computed NVM address, + * serialized record size, runtime loaded flag, and parameter name when name + * support is enabled. + * + * The slot-to-parameter relationship is derived from the compile-time + * persistent mapping table, while the loaded flag reflects whether the slot + * was loaded successfully during the current NVM initialization/load flow. + * + * @return ePAR_OK Debug information was printed. + * @return ePAR_ERROR Debug print is disabled at build time. + */ +par_status_t par_nvm_print_nvm_lut(void) +{ + par_status_t status = ePAR_OK; + +#if (1 == PAR_CFG_DEBUG_EN) + PAR_DBG_PRINT("PAR_NVM: Parameter NVM look-up table:"); + PAR_DBG_PRINT(" # ID Addr Size Loaded name"); + PAR_DBG_PRINT("================================================"); + + for (uint16_t persist_idx = 0U; persist_idx < PAR_PERSISTENT_COMPILE_COUNT; persist_idx++) + { + const par_num_t par_num = g_par_persist_slot_to_par_num[persist_idx]; + PAR_DBG_PRINT( + " %u %u 0x%08lX %lu %u %s", + (unsigned)persist_idx, + (unsigned)PAR_NVM_CFG_ID_VALUE(par_get_config(par_num)), + (unsigned long)par_nvm_addr_from_persist_idx(persist_idx), + (unsigned long)gp_layout->record_size_from_par_num(par_num), + (unsigned)g_par_nvm_slot_runtime.loaded_slots[persist_idx], + PAR_NVM_DBG_NAME_ARG(par_get_config(par_num))); + } +#else + status = ePAR_ERROR; +#endif + + return status; +} + +/** + * @brief Clear the runtime-loaded persistent-slot view. + */ +static void par_nvm_clear_lut(void) +{ + (void)memset(&g_par_nvm_slot_runtime, 0, sizeof(g_par_nvm_slot_runtime)); +} +/** + * @brief Mark every compiled persistent slot as available in the runtime view. + * + * @details This helper is used after a full rewrite path where the managed NVM + * image is regenerated for the current compile-time persistent schema. + */ +static void par_nvm_build_new_nvm_lut(void) +{ + par_nvm_clear_lut(); + (void)memset(g_par_nvm_slot_runtime.loaded_slots, true, sizeof(g_par_nvm_slot_runtime.loaded_slots)); + g_par_nvm_slot_runtime.loaded_count = PAR_PERSISTENT_COMPILE_COUNT; + par_nvm_print_nvm_lut(); +} +/** + * @brief Get parameter NVM object start address from the compile-time persistent slot. + * + * @param par_num Live parameter number. + * @return NVM address of the persistent slot, or 0 when mapping is invalid. + */ +static uint32_t par_nvm_get_nvm_lut_addr(const par_num_t par_num) +{ + const par_cfg_t * const par_cfg = par_get_config(par_num); + + if ((NULL == par_cfg) || (false == par_cfg->persistent) || (par_cfg->persist_idx >= PAR_PERSISTENT_COMPILE_COUNT)) + { + return 0U; + } + + return par_nvm_addr_from_persist_idx(par_cfg->persist_idx); +} + +/** + * @brief Load all parameter values from NVM. + * + * @details Two stored-count asymmetries are handled explicitly: + * - If the stored header count is greater than the compile-time persistent + * slot count, the image is treated as incompatible and the caller rebuilds + * the managed NVM area from current defaults. + * - If the stored header count is smaller than the compile-time persistent + * slot count, compatible layouts restore the stored prefix first and append + * the missing tail slots from live defaults before rewriting the header + * count. The grouped payload-only layout is excluded from that repair path + * and rebuilds on any stored/live count mismatch. + * + * @param num_of_par Number of stored parameters inside NVM. + * @return Status of operation. + */ +static par_status_t par_nvm_load_all(const uint16_t num_of_par) +{ + /* Hold context for one load-path validation failure. */ + typedef struct + { + const char *reason; /**< Short description of the validation failure. */ + uint16_t stored_id; /**< Stored ID reported by the selected layout, when available. */ + } par_nvm_load_error_ctx_t; + + par_status_t status = ePAR_OK; + par_status_t op_status = ePAR_OK; + par_num_t par_num = 0U; + uint16_t i = 0U; + par_nvm_data_obj_t obj_data = { 0 }; + uint16_t new_par_cnt = 0U; + par_nvm_load_error_ctx_t err = { 0 }; + + par_nvm_clear_lut(); + + /* TODO: Revisit deployed-schema migration for persistent-slot shrink/reorder. + * Consider introducing tombstone/delete-marker handling so removed slots can be represented + * without forcing a destructive rebuild when relaxed compatibility rules are desired. + */ + if (num_of_par > PAR_PERSISTENT_COMPILE_COUNT) + { + status = ePAR_ERROR; + i = PAR_PERSISTENT_COMPILE_COUNT; + err.reason = "stored-count-overflow"; + op_status = status; + goto out; + } + + /* Restore the stored prefix that still exists in the current compile-time schema. */ + for (i = 0U; i < num_of_par; i++) + { + const par_cfg_t *par_cfg = NULL; + const uint32_t obj_addr = par_nvm_addr_from_persist_idx(i); + + op_status = par_nvm_get_num_by_persist_idx(i, &par_num); + if (ePAR_OK != op_status) + { + status = ePAR_ERROR; + err.reason = "persist-slot-invalid"; + goto out; + } + + par_cfg = par_get_config(par_num); + if ((NULL == par_cfg) || (false == par_cfg->persistent) || (par_cfg->persist_idx != i)) + { + status = ePAR_ERROR; + err.reason = "persist-map-invalid"; + op_status = status; + goto out; + } + + op_status = gp_layout->read(gp_store, obj_addr, par_num, &obj_data); + if (ePAR_OK != op_status) + { + status = op_status; + err.reason = (ePAR_ERROR_CRC == op_status) ? "crc-mismatch" : "read-failed"; + err.stored_id = gp_layout->get_error_stored_id(par_num, &obj_data); + goto out; + } + + op_status = gp_layout->validate_loaded_obj(par_num, &obj_data, &err.reason, &err.stored_id); + if (ePAR_OK != op_status) + { + status = ePAR_ERROR; + if (NULL == err.reason) + { + err.reason = "layout-validate-failed"; + } + goto out; + } + + op_status = par_set_fast(par_num, &obj_data.data); + if (ePAR_OK != op_status) + { + status |= op_status; + err.reason = "restore-failed"; + err.stored_id = gp_layout->get_error_stored_id(par_num, &obj_data); + goto out; + } + + g_par_nvm_slot_runtime.loaded_slots[i] = true; + } + + /* Append any newly introduced persistent slots after restoring the stored prefix. */ + for (i = num_of_par; i < PAR_PERSISTENT_COMPILE_COUNT; i++) + { + const par_cfg_t *par_cfg = NULL; + + op_status = par_nvm_get_num_by_persist_idx(i, &par_num); + if (ePAR_OK != op_status) + { + status = ePAR_ERROR; + err.reason = "persist-slot-invalid"; + goto out; + } + + par_cfg = par_get_config(par_num); + if ((NULL == par_cfg) || (false == par_cfg->persistent) || (par_cfg->persist_idx != i)) + { + status = ePAR_ERROR; + err.reason = "persist-map-invalid"; + op_status = status; + goto out; + } + + status |= par_save(par_num); + if (ePAR_OK != (status & ePAR_STATUS_ERROR_MASK)) + { + err.reason = "append-save-failed"; + err.stored_id = (uint16_t)PAR_NVM_CFG_ID_VALUE(par_cfg); + op_status = status; + goto out; + } + + g_par_nvm_slot_runtime.loaded_slots[i] = true; + new_par_cnt++; + } + g_par_nvm_slot_runtime.loaded_count = i; + + if (new_par_cnt > 0U) + { + /* Missing stored slots were appended from live defaults; commit the new count. */ + status |= par_nvm_write_header(PAR_PERSISTENT_COMPILE_COUNT); + if (ePAR_OK != (status & ePAR_STATUS_ERROR_MASK)) + { + err.reason = "rewrite-header-failed"; + op_status = status; + goto out; + } + + PAR_INFO_PRINT("PAR_NVM: appended %u new persistent slots and rewrote header count to %u", + (unsigned)new_par_cnt, + (unsigned)PAR_PERSISTENT_COMPILE_COUNT); + } + +out: +#if (1 == PAR_CFG_DEBUG_EN) + PAR_INFO_PRINT("PAR_NVM: load-all finished with status=%s", par_get_status_str(status)); + PAR_INFO_PRINT("PAR_NVM: stored parameter count in NVM=%u", (unsigned)num_of_par); + PAR_INFO_PRINT("PAR_NVM: live persistent parameter count=%u", (unsigned)PAR_PERSISTENT_COMPILE_COUNT); + + if (NULL != err.reason) + { + PAR_DBG_PRINT( + "PAR_NVM: load error slot=%u, addr=0x%08lX, stored_id=%u, par_num=%u, expected_id=%u, name=%s, reason=%s, op_status=%s, final_status=%s", + (unsigned)i, + (unsigned long)par_nvm_addr_from_persist_idx(i), + (unsigned)err.stored_id, + (unsigned)((i < PAR_PERSIST_SLOT_MAP_CAPACITY) ? g_par_persist_slot_to_par_num[i] : 0U), + (unsigned)((i < PAR_PERSIST_SLOT_MAP_CAPACITY) ? PAR_NVM_CFG_ID_VALUE(par_get_config(g_par_persist_slot_to_par_num[i])) : 0U), + PAR_NVM_DBG_NAME_ARG((i < PAR_PERSIST_SLOT_MAP_CAPACITY) ? par_get_config(g_par_persist_slot_to_par_num[i]) : NULL), + err.reason, + par_get_status_str(op_status), + par_get_status_str(status)); + } +#endif + return status; +} +/** + * @brief Bind or prepare the active parameter-storage backend. + * + * @details This weak default keeps existing backends source-compatible when + * they do not need an explicit pre-bind step. Backends that must attach one + * concrete port or device context should provide a strong override. + * + * @return ePAR_OK because the default implementation has no work to perform. + */ +PAR_PORT_WEAK par_status_t par_store_backend_bind(void) +{ + return ePAR_OK; +} +/** + * @brief Resolve, validate, and initialize the mounted storage backend. + * + * @return Status of operation. + */ +static par_status_t par_nvm_init_nvm(void) +{ + par_status_t status = ePAR_OK; + bool is_nvm_init = false; + + PAR_DBG_PRINT("PAR_NVM: resolving storage backend"); + status = par_store_backend_bind(); + if (ePAR_OK != status) + { + PAR_ERR_PRINT("PAR_NVM: backend bind failed, err=%u", (unsigned)status); + status = ePAR_ERROR_INIT; + } + + if (ePAR_OK == status) + { + gp_store = par_store_backend_get_api(); + } + gp_layout = par_nvm_layout_init(); + gb_is_nvm_owner = false; + + if ((NULL == gp_layout) || (NULL == gp_layout->record_size_from_par_num) || + (NULL == gp_layout->addr_from_persist_idx) || (NULL == gp_layout->populate_data_obj) || + (NULL == gp_layout->read) || (NULL == gp_layout->write) || + (NULL == gp_layout->validate_loaded_obj) || (NULL == gp_layout->get_error_stored_id) || + (NULL == gp_layout->check_compat) +#if (1 == PAR_CFG_NVM_WRITE_VERIFY_EN) + || (NULL == gp_layout->data_obj_matches) +#endif + ) + { + PAR_ERR_PRINT("PAR_NVM: no valid persisted-record layout adapter is wired"); + status = ePAR_ERROR_INIT; + } + + /* Validate the mounted backend once before any operation callback is used. */ + if ((ePAR_OK == status) && + ((NULL == gp_store) || (NULL == gp_store->init) || (NULL == gp_store->deinit) || + (NULL == gp_store->is_init) || (NULL == gp_store->read) || (NULL == gp_store->write) || + (NULL == gp_store->erase) || (NULL == gp_store->sync))) + { + PAR_ERR_PRINT("PAR_NVM: no valid parameter storage backend is wired"); + status = ePAR_ERROR_INIT; + } + + if (ePAR_OK == status) + { + (void)gp_store->is_init(&is_nvm_init); + } + + if ((ePAR_OK == status) && (false == is_nvm_init)) + { + const par_status_t store_status = gp_store->init(); + if (ePAR_OK != store_status) + { + status = ePAR_ERROR_INIT; + PAR_ERR_PRINT("PAR_NVM: backend init failed, err=%u", (unsigned)store_status); + } + else + { + gb_is_nvm_owner = true; + PAR_DBG_PRINT("PAR_NVM: backend initialized by parameter module"); + } + } + else if (ePAR_OK == status) + { + PAR_DBG_PRINT("PAR_NVM: reusing already-initialized storage backend"); + } + + return status; +} +/** + * @} + */ + +/** + * @addtogroup API_PAR_NVM_FUNCTIONS + * @{ + * + * @brief Following functions are part of Device Parameter NVM manipulation API. + */ + +/** + * @brief Initialize parameter NVM handling. + * @details Initialization behavior depends on the settings in par_cfg.h. + * Table-ID checking is enabled only when PAR_CFG_TABLE_ID_CHECK_EN is set. + * + * The recovery flow is centralized and cumulative: + * - header validation runs first; + * - table-ID validation runs only when the header is valid; + * - header CRC validation covers both the stored object count and the stored + * table-ID digest bytes; + * - payload loading runs only when both checks pass; + * - then the collected error bits decide whether startup should restore live + * RAM values to defaults only, or restore defaults and also rebuild the + * managed NVM image. + * + * This keeps the recovery action aligned with the detected failure class + * instead of hiding it inside a long if-else chain. + * + * @return Status of initialization. + */ +par_status_t par_nvm_init(void) +{ + par_status_t status = ePAR_OK; + par_status_t detect_status = ePAR_OK; + par_nvm_head_obj_t head_obj = { 0 }; + uint16_t per_par_nb = 0U; + bool need_set_default = false; + bool need_rewrite_nvm = false; + + PAR_DBG_PRINT("PAR_NVM: initialization started"); + status = par_nvm_init_nvm(); + if (ePAR_OK != status) + { + return status; + } + + gb_is_init = true; + per_par_nb = PAR_PERSISTENT_COMPILE_COUNT; + + if (per_par_nb == 0U) + { + PAR_INFO_PRINT("PAR_NVM: no persistent parameters configured"); + return (par_status_t)(status | ePAR_WAR_NO_PERSISTENT); + } + + /* Step 1: validate header */ + detect_status = par_nvm_validate_header(&head_obj); + +#if (1 == PAR_CFG_TABLE_ID_CHECK_EN) + /* Step 2: validate table compatibility only when header is valid */ + if (ePAR_OK == (detect_status & ePAR_STATUS_ERROR_MASK)) + { + const par_nvm_compat_result_t compat = gp_layout->check_compat(&head_obj); + + if (ePAR_NVM_COMPAT_REBUILD == compat) + { + detect_status |= ePAR_ERROR_TABLE_ID; + } + else + { + if (ePAR_NVM_COMPAT_PREFIX_APPEND == compat) + { + PAR_INFO_PRINT("PAR_NVM: stored persistent prefix is compatible, restore=%u append=%u", + (unsigned)head_obj.obj_nb, + (unsigned)((uint16_t)PAR_PERSISTENT_COMPILE_COUNT - head_obj.obj_nb)); + } + + detect_status |= par_nvm_load_all(head_obj.obj_nb); + } + } +#else + /* Step 2: load payload only when header is valid */ + if (ePAR_OK == (detect_status & ePAR_STATUS_ERROR_MASK)) + { + detect_status |= par_nvm_load_all(head_obj.obj_nb); + } +#endif + + /* Step 4: classify recovery action from detected issues */ + if (0U != (detect_status & ePAR_ERROR_TABLE_ID)) + { + PAR_WARN_PRINT("PAR_NVM: table-ID mismatch detected, restoring defaults and rebuilding managed NVM image"); + need_set_default = true; + need_rewrite_nvm = true; + } + + if (0U != (detect_status & ePAR_ERROR_CRC)) + { + PAR_WARN_PRINT("PAR_NVM: CRC corruption detected, rebuilding NVM from defaults"); + need_set_default = true; + need_rewrite_nvm = true; + } + + /* + * ePAR_ERROR here represents generic header/signature validation failure + * from par_nvm_validate_header(). + */ + if (0U != (detect_status & ePAR_ERROR)) + { + PAR_WARN_PRINT("PAR_NVM: header/signature mismatch detected, rebuilding NVM from defaults"); + need_set_default = true; + need_rewrite_nvm = true; + } + + /* + * NVM access failures are not considered fully recoverable. + * Restore live values to defaults, but keep ePAR_ERROR_NVM in final status. + */ + if (0U != (detect_status & ePAR_ERROR_NVM)) + { + PAR_WARN_PRINT("PAR_NVM: NVM access error detected, restoring live values to defaults without forced rewrite"); + need_set_default = true; + } + + /* Step 5: perform recovery */ + if (true == need_set_default) + { + status |= par_set_all_to_default(); + status |= ePAR_WAR_SET_TO_DEF; + } + + if (true == need_rewrite_nvm) + { + status |= par_nvm_reset_all(); + status |= ePAR_WAR_NVM_REWRITTEN; + } + + /* + * Step 6: finalize returned status. + * + * Recoverable problems (header/signature mismatch, CRC mismatch, + * table-ID mismatch) should not remain as error bits once recovery + * succeeded. Keep only warnings for those cases. + * + * Non-recoverable backend/storage access failures remain reported. + */ + if (0U != (detect_status & ePAR_ERROR_NVM)) + { + status |= ePAR_ERROR_NVM; + } + + PAR_INFO_PRINT("PAR_NVM: initialization finished with status=%s", par_get_status_str(status)); + return status; +} +/** + * @brief De-Initialize parameter NVM handling. + * + * @return Status of de-init. + */ +par_status_t par_nvm_deinit(void) +{ + par_status_t status = ePAR_OK; + + PAR_DBG_PRINT("PAR_NVM: deinitialization started"); + if (true == gb_is_init) + { + if (true == gb_is_nvm_owner) + { + const par_status_t store_status = gp_store->deinit(); + if (ePAR_OK != store_status) + { + status = ePAR_ERROR; + PAR_ERR_PRINT("PAR_NVM: backend deinit failed, err=%u", (unsigned)store_status); + } + } + + if (ePAR_OK == status) + { + gb_is_init = false; + gb_is_nvm_owner = false; + gp_store = NULL; + gp_layout = NULL; + } + } + else + { + status = ePAR_ERROR; + } + + PAR_INFO_PRINT("PAR_NVM: deinitialization finished with status=%s", par_get_status_str(status)); + return status; +} +/** + * @brief Store parameter value to NVM. + * + * @note Sync has only effect when using EEPROM emulated NVM feature! When. + * using Flash end memory device. + * + * @note In case of using Flash end memory for storing parameters take special. + * care when enabling sync (nvm_sync=true). At each sync data from RAM. + * is copied to FLASH. + * + * @param par_num Parameter enumeration number. + * @param nvm_sync Request an explicit backend sync after parameter write. When + * PAR_CFG_NVM_WRITE_VERIFY_EN is enabled, write verification also + * forces a backend sync before the readback step even if this flag is + * false. Backend implementations may also persist data before this + * function returns when their write contract requires completed writes + * to be durable on success. Therefore false does not mean RAM-only + * staging, and a successful return means the backend-side work needed + * for this request has completed. This flag still does not guarantee + * transactional atomicity across any backend-specific internal + * chunking. + * @return Status of operation. + */ +par_status_t par_nvm_write(const par_num_t par_num, const bool nvm_sync) +{ + if (true != gb_is_init) + { + return ePAR_ERROR_INIT; + } + + if (par_num >= ePAR_NUM_OF) + { + return ePAR_ERROR; + } + + const par_cfg_t * const par_cfg = par_get_config(par_num); + if (true != par_cfg->persistent) + { + PAR_DBG_PRINT("PAR_NVM: skip write for non-persistent parameter, par_num=%u", (unsigned)par_num); + return ePAR_ERROR; + } + + par_status_t status = ePAR_OK; + par_nvm_data_obj_t obj_data = { 0 }; + par_type_t live_data = { 0 }; + uint32_t par_addr = 0UL; + par_status_t store_status = ePAR_OK; + + PAR_DBG_PRINT("PAR_NVM: writing persistent parameter, par_num=%u id=%u", (unsigned)par_num, (unsigned)PAR_NVM_CFG_ID_VALUE(par_cfg)); + (void)par_get(par_num, &live_data); + gp_layout->populate_data_obj(par_num, &live_data, &obj_data); + par_addr = par_nvm_get_nvm_lut_addr(par_num); + store_status = gp_layout->write(gp_store, par_addr, par_num, &obj_data); + if (ePAR_OK != store_status) + { + status |= ePAR_ERROR_NVM; + PAR_ERR_PRINT("PAR_NVM: parameter write failed, par_num=%u id=%u addr=0x%08lX err=%u", + (unsigned)par_num, + (unsigned)PAR_NVM_CFG_ID_VALUE(par_cfg), + (unsigned long)par_addr, + (unsigned)store_status); + } + + if (ePAR_OK == (status & ePAR_STATUS_ERROR_MASK)) + { +#if (1 == PAR_CFG_NVM_WRITE_VERIFY_EN) + (void)nvm_sync; + status |= par_nvm_sync_backend("before parameter readback verify"); + if (ePAR_OK == (status & ePAR_STATUS_ERROR_MASK)) + { + status |= par_nvm_verify_record_readback(par_addr, par_num, &obj_data); + if (ePAR_OK != (status & ePAR_STATUS_ERROR_MASK)) + { + PAR_ERR_PRINT("PAR_NVM: parameter readback verification failed, par_num=%u id=%u addr=0x%08lX err=%u", + (unsigned)par_num, + (unsigned)PAR_NVM_CFG_ID_VALUE(par_cfg), + (unsigned long)par_addr, + (unsigned)status); + } + } +#else + if (true == nvm_sync) + { + status |= par_nvm_sync_backend("after parameter write"); + } +#endif + } + + return status; +} +/** + * @brief Store all parameter value to NVM. + * + * @return Status of operation. + */ +par_status_t par_nvm_write_all(void) +{ + if (true != gb_is_init) + { + return ePAR_ERROR_INIT; + } + + par_status_t status = ePAR_OK; + PAR_DBG_PRINT("PAR_NVM: storing all persistent parameters to NVM"); + + /* Mark the header invalid before bulk rewrite and commit that state. */ + const par_status_t store_status = gp_store->erase(PAR_NVM_HEAD_SIGN_ADDR, PAR_NVM_HEAD_SIGN_SIZE); + if (ePAR_OK != store_status) + { + status |= ePAR_ERROR_NVM; + PAR_ERR_PRINT("PAR_NVM: signature erase failed, err=%u", (unsigned)store_status); + } + + if (ePAR_OK == (status & ePAR_STATUS_ERROR_MASK)) + { + for (par_num_t par_num = 0U; par_num < ePAR_NUM_OF; par_num++) + { + if (true == par_is_persistent(par_num)) + { + status |= par_nvm_write(par_num, false); + if (ePAR_OK != (status & ePAR_STATUS_ERROR_MASK)) + { + PAR_ERR_PRINT("PAR_NVM: bulk write aborted, par_num=%u id=%u addr=0x%08lX err=%u", + (unsigned)par_num, + (unsigned)PAR_NVM_CFG_ID_VALUE(par_get_config(par_num)), + (unsigned long)par_nvm_get_nvm_lut_addr(par_num), + (unsigned)status); + break; + } + } + } + } + + /* Restore a valid header only after the full rewrite completes successfully. */ + if (ePAR_OK == (status & ePAR_STATUS_ERROR_MASK)) + { + status |= par_nvm_write_header(PAR_PERSISTENT_COMPILE_COUNT); + } + + if (ePAR_OK == (status & ePAR_STATUS_ERROR_MASK)) + { + par_nvm_build_new_nvm_lut(); + } + else + { + par_nvm_clear_lut(); + } + + PAR_INFO_PRINT("PAR_NVM: store-all finished with status=%s", par_get_status_str(status)); + + return status; +} +/** + * @brief Rewrite the whole parameter NVM section. + * @details The signature is corrupted first to mark the image as being + * rewritten, then the LUT and all persistent objects are rebuilt. + * @return Status of operation. + */ +par_status_t par_nvm_reset_all(void) +{ + par_status_t status = ePAR_OK; + + PAR_ASSERT(true == gb_is_init); + PAR_DBG_PRINT("PAR_NVM: rebuild-all requested"); + + if (true != gb_is_init) + { + return ePAR_ERROR_INIT; + } + + status |= par_nvm_write_all(); + if (ePAR_OK == (status & ePAR_STATUS_ERROR_MASK)) + { + PAR_DBG_PRINT("PAR_NVM: rebuild-all finished successfully"); + } + else + { + PAR_ERR_PRINT("PAR_NVM: rebuild-all failed, status=%s", par_get_status_str(status)); + } + + return status; +} +/** + * @} + */ + +#endif /* 1 == PAR_CFG_NVM_EN */ diff --git a/src/persist/par_nvm.h b/src/persist/par_nvm.h new file mode 100644 index 0000000..fda18aa --- /dev/null +++ b/src/persist/par_nvm.h @@ -0,0 +1,80 @@ +/** + * @file par_nvm.h + * @brief Declare non-volatile storage support for parameters. + * @author Ziga Miklosic + * @version V3.0.1 + * @date 2026-01-29 + * + * @copyright Copyright (c) 2026 Ziga Miklosic. Distributed under the MIT license. + * + * @note : + * @par Change Log: + * Date Version Author Description + * 2026-01-29 V3.0.1 Ziga Miklosic first version + */ +/** + * @addtogroup PAR_NVM + * @{ + */ + +#ifndef _PAR_NVM_H_ +#define _PAR_NVM_H_ +/** + * @brief Include dependencies. + */ +#include +#include +#include + +#include "par.h" + +#if (1 == PAR_CFG_NVM_EN) +/** + * @brief Function declarations. + */ +/** + * @brief Initialize parameter non-volatile storage support. + * @return Operation status. + */ +par_status_t par_nvm_init(void); +/** + * @brief Deinitialize parameter non-volatile storage support. + * @return Operation status. + */ +par_status_t par_nvm_deinit(void); +/** + * @brief Persist one parameter to non-volatile storage. + * @param par_num Parameter number. + * @param nvm_sync Request an explicit backend sync after the write when true. + * Some backends may still persist data before this call returns even + * when this flag is false, as long as the backend-specific API contract + * allows it. A successful call still means that the backend has + * finished the persistence work required by its write contract for this + * request. This flag does not guarantee that false means RAM-only + * staging, and it also does not guarantee transactional atomicity + * across any backend-specific internal chunking. + * @return Operation status. + */ +par_status_t par_nvm_write(const par_num_t par_num, const bool nvm_sync); +/** + * @brief Persist all persistent parameters to non-volatile storage. + * @return Operation status. + */ +par_status_t par_nvm_write_all(void); +/** + * @brief Erase all parameter data managed by the NVM backend. + * @return Operation status. + */ +par_status_t par_nvm_reset_all(void); +/** + * @brief Print the internal NVM lookup table for diagnostics. + * @return Operation status. + */ +par_status_t par_nvm_print_nvm_lut(void); + +#endif /* 1 == PAR_CFG_NVM_EN */ +/** + * @} + */ + +#endif /* _PAR_NVM_H_ */ diff --git a/src/persist/par_nvm_cfg.h b/src/persist/par_nvm_cfg.h new file mode 100644 index 0000000..6bd81ee --- /dev/null +++ b/src/persist/par_nvm_cfg.h @@ -0,0 +1,126 @@ +/** + * @file par_nvm_cfg.h + * @brief Provide compile-time configuration defaults for parameter persistence. + * @author wdfk-prog + * @version 1.0 + * @date 2026-04-19 + * + * @copyright Copyright (c) 2026 Ziga Miklosic. Distributed under the MIT license. + * + * @note : + * @par Change Log: + * Date Version Author Description + * 2026-04-19 1.0 wdfk-prog first version + */ +#ifndef _PAR_NVM_CFG_H_ +#define _PAR_NVM_CFG_H_ + +/** + * @brief Enable/Disable storing persistent parameters to NVM. + * + * @note This switch is also the single compile-time gate for persistence + * metadata in the parameter table. There is no separate PAR_CFG_ENABLE_PERSIST + * override anymore. + */ +#ifndef PAR_CFG_NVM_EN +#define PAR_CFG_NVM_EN (1) +#endif + +/** + * @brief Enable/Disable the legacy GeneralEmbeddedCLibraries/nvm backend. + * + * @note Keep disabled when the application provides an out-of-package storage + * backend, such as the RT-Thread AT24CXX adapter. + */ +#ifndef PAR_CFG_NVM_BACKEND_GEL_EN +#define PAR_CFG_NVM_BACKEND_GEL_EN (0) +#endif + +/** + * @brief Enable/Disable parameter-table compatibility checking. + * + * @note The stored NVM image header carries a table-ID digest that covers + * PAR_CFG_TABLE_ID_SCHEMA_VER, selected record layout, and the stored + * persistent prefix. Self-describing layouts include parameter IDs in that + * digest. Payload-only layouts intentionally exclude external parameter IDs + * and hash only prefix count, persistent order, and parameter type so stored + * prefixes with identical byte layout remain compatible. + * + * When enabled, any persisted-layout incompatibility is treated as a managed + * schema change: startup restores defaults and rebuilds the managed NVM image. + * Layouts with stable prefix addresses allow compatible tail-slot growth when + * the stored prefix still matches the live prefix. The grouped payload-only + * layout is excluded from that repair path and rebuilds on any stored/live + * count mismatch. Payload-only layouts therefore still require the integrator + * to bump PAR_CFG_TABLE_ID_SCHEMA_VER whenever a prefix parameter is + * semantically remapped without changing its serialized byte layout. + * + * @pre "PAR_CFG_NVM_EN" must be enabled, otherwise table-ID checking does. + * not apply. + */ +#ifndef PAR_CFG_TABLE_ID_CHECK_EN +#define PAR_CFG_TABLE_ID_CHECK_EN (0) +#endif + +/** + * @brief Parameter-table ID schema version. + * + * @note Increase this value when the serialized table-ID composition changes. + * The integrator owns this version number and may override it in + * port/par_cfg_port.h before this header provides the default. + */ +#ifndef PAR_CFG_TABLE_ID_SCHEMA_VER +#define PAR_CFG_TABLE_ID_SCHEMA_VER (1U) +#endif + +#include "persist/backend/par_store_backend_flash_ee_cfg.h" + +/** + * @brief Enable/Disable write-path readback verification for persisted data. + * + * @details When enabled, each persisted-record write and each header write are + * followed by a backend sync and a readback verification step. This improves + * reliability at the cost of additional latency and backend traffic. + */ +#ifndef PAR_CFG_NVM_WRITE_VERIFY_EN +#define PAR_CFG_NVM_WRITE_VERIFY_EN (0) +#endif + +/** + * @brief Select persisted record layout. + * + * @note The chosen layout is also included in the table-ID digest so layout + * changes are treated as managed compatibility changes. + */ +#define PAR_CFG_NVM_RECORD_LAYOUT_FIXED_SLOT_WITH_SIZE (0U) +#define PAR_CFG_NVM_RECORD_LAYOUT_FIXED_SLOT_NO_SIZE (1U) +#define PAR_CFG_NVM_RECORD_LAYOUT_COMPACT_PAYLOAD (2U) +#define PAR_CFG_NVM_RECORD_LAYOUT_FIXED_PAYLOAD_ONLY (3U) +#define PAR_CFG_NVM_RECORD_LAYOUT_GROUPED_PAYLOAD_ONLY (4U) + +#ifndef PAR_CFG_NVM_RECORD_LAYOUT +#define PAR_CFG_NVM_RECORD_LAYOUT (PAR_CFG_NVM_RECORD_LAYOUT_FIXED_SLOT_WITH_SIZE) +#endif + +/** + * @brief Derived layout capability: serialized records store a parameter ID. + */ +#if (PAR_CFG_NVM_RECORD_LAYOUT == PAR_CFG_NVM_RECORD_LAYOUT_FIXED_SLOT_WITH_SIZE) || \ + (PAR_CFG_NVM_RECORD_LAYOUT == PAR_CFG_NVM_RECORD_LAYOUT_FIXED_SLOT_NO_SIZE) || \ + (PAR_CFG_NVM_RECORD_LAYOUT == PAR_CFG_NVM_RECORD_LAYOUT_COMPACT_PAYLOAD) +#define PAR_CFG_NVM_RECORD_LAYOUT_HAS_STORED_ID (1) +#else +#define PAR_CFG_NVM_RECORD_LAYOUT_HAS_STORED_ID (0) +#endif + +/** + * @brief Derived layout capability: serialized records store a size descriptor. + */ +#if (PAR_CFG_NVM_RECORD_LAYOUT == PAR_CFG_NVM_RECORD_LAYOUT_FIXED_SLOT_WITH_SIZE) || \ + (PAR_CFG_NVM_RECORD_LAYOUT == PAR_CFG_NVM_RECORD_LAYOUT_COMPACT_PAYLOAD) +#define PAR_CFG_NVM_RECORD_LAYOUT_HAS_SIZE_DESC (1) +#else +#define PAR_CFG_NVM_RECORD_LAYOUT_HAS_SIZE_DESC (0) +#endif + +#endif /* _PAR_NVM_CFG_H_ */ diff --git a/src/persist/par_nvm_layout.h b/src/persist/par_nvm_layout.h new file mode 100644 index 0000000..8fb87b0 --- /dev/null +++ b/src/persist/par_nvm_layout.h @@ -0,0 +1,301 @@ +/** + * @file par_nvm_layout.h + * @brief Declare private persisted-record layout interfaces. + * @author wdfk-prog + * @version 1.2 + * @date 2026-04-13 + * + * @copyright Copyright (c) 2026 Ziga Miklosic. Distributed under the MIT license. + * + * @note : + * @par Change Log: + * Date Version Author Description + * 2026-04-06 1.0 wdfk-prog first version + * 2026-04-11 1.1 wdfk-prog split layout structs by selected NVM layout + * 2026-04-13 1.2 wdfk-prog add layout-ops registration for NVM core + */ +#ifndef _PAR_NVM_LAYOUT_H_ +#define _PAR_NVM_LAYOUT_H_ + +#include +#include + +#include "par.h" +#include "persist/backend/par_store_backend.h" + +#if (1 == PAR_CFG_NVM_EN) + +#define PAR_NVM_RECORD_ID_SIZE ((uint32_t)sizeof(uint16_t)) +#define PAR_NVM_RECORD_SIZE_FIELD_SIZE ((uint32_t)sizeof(uint8_t)) +#define PAR_NVM_RECORD_CRC_SIZE ((uint32_t)sizeof(uint8_t)) +#define PAR_NVM_RECORD_DATA_SLOT_SIZE ((uint8_t)sizeof(par_type_t)) + +/** + * @brief Parameter NVM header object. + * + * @details This private header shape is shared between the common NVM flow and + * the selected record-layout adapter so layout-specific compatibility policy + * can be implemented below the top-level `par_nvm.c` logic. + */ +typedef struct +{ + uint32_t sign; /**< Signature in host order. */ + uint16_t obj_nb; /**< Stored data object number in host order. */ + uint32_t table_id; /**< Stored parameter-table ID in platform-native order. */ + uint16_t crc; /**< Header CRC-16 over serialized obj_nb and table_id. */ +} par_nvm_head_obj_t; + +/** + * @brief Result of comparing the stored NVM image against the live schema. + */ +typedef enum +{ + ePAR_NVM_COMPAT_REBUILD = 0, /**< Stored image is incompatible and must be rebuilt. */ + ePAR_NVM_COMPAT_EXACT_MATCH, /**< Stored image matches the live schema exactly. */ + ePAR_NVM_COMPAT_PREFIX_APPEND /**< Stored prefix is compatible and new tail slots may be appended. */ +} par_nvm_compat_result_t; + +#if (PAR_CFG_NVM_RECORD_LAYOUT == PAR_CFG_NVM_RECORD_LAYOUT_FIXED_SLOT_WITH_SIZE) +/** + * @brief Selected fixed-slot layout with explicit size descriptor. + */ +typedef struct +{ + uint16_t id; /**< Parameter ID. */ + uint8_t size; /**< Serialized payload-size descriptor. */ + uint8_t crc; /**< CRC-8 over id, size, and payload bytes. */ + par_type_t data; /**< Fixed 4-byte payload slot. */ +} par_nvm_layout_fixed_slot_with_size_record_t; + +typedef struct +{ + uint16_t id; /**< Parameter ID. */ + uint8_t size; /**< Serialized payload-size descriptor. */ + par_type_t data; /**< Canonical parameter value. */ +} par_nvm_layout_fixed_slot_with_size_data_obj_t; + +typedef par_nvm_layout_fixed_slot_with_size_data_obj_t par_nvm_data_obj_t; +#elif (PAR_CFG_NVM_RECORD_LAYOUT == PAR_CFG_NVM_RECORD_LAYOUT_FIXED_SLOT_NO_SIZE) +/** + * @brief Selected fixed-slot layout without a size descriptor. + * + * @note The serialized image is 7 bytes: id[2] + crc[1] + payload[4]. + * Do not derive the persisted size from sizeof(this type) because some + * compilers may append trailing padding to the in-memory view. + */ +typedef struct +{ + uint16_t id; /**< Parameter ID. */ + uint8_t crc; /**< CRC-8 over id and payload bytes. */ + uint8_t payload[PAR_NVM_RECORD_DATA_SLOT_SIZE]; /**< Fixed 4-byte payload slot. */ +} par_nvm_layout_fixed_slot_no_size_record_t; + +typedef struct +{ + uint16_t id; /**< Parameter ID. */ + par_type_t data; /**< Canonical parameter value. */ +} par_nvm_layout_fixed_slot_no_size_data_obj_t; + +typedef par_nvm_layout_fixed_slot_no_size_data_obj_t par_nvm_data_obj_t; +#elif (PAR_CFG_NVM_RECORD_LAYOUT == PAR_CFG_NVM_RECORD_LAYOUT_COMPACT_PAYLOAD) +/** + * @brief Selected compact layout with id, size, crc, and variable payload bytes. + * + * @note The payload width is variable. Only the first @ref size bytes inside + * @ref payload belong to the serialized record. + */ +typedef struct +{ + uint16_t id; /**< Parameter ID. */ + uint8_t size; /**< Serialized payload-size descriptor. */ + uint8_t crc; /**< CRC-8 over id, size, and payload bytes. */ + uint8_t payload[PAR_NVM_RECORD_DATA_SLOT_SIZE]; /**< Maximum payload storage. */ +} par_nvm_layout_compact_payload_record_t; + +typedef struct +{ + uint16_t id; /**< Parameter ID. */ + uint8_t size; /**< Serialized payload-size descriptor. */ + par_type_t data; /**< Canonical parameter value. */ +} par_nvm_layout_compact_payload_data_obj_t; + +typedef par_nvm_layout_compact_payload_data_obj_t par_nvm_data_obj_t; +#elif (PAR_CFG_NVM_RECORD_LAYOUT == PAR_CFG_NVM_RECORD_LAYOUT_FIXED_PAYLOAD_ONLY) +/** + * @brief Selected fixed persistent-order payload-only layout. + * + * @note Only crc + active payload bytes are serialized. The persisted record + * does not contain an ID field or a size descriptor. + */ +typedef struct +{ + uint8_t crc; /**< CRC-8 over payload bytes. */ + uint8_t payload[PAR_NVM_RECORD_DATA_SLOT_SIZE]; /**< Maximum payload storage. */ +} par_nvm_layout_fixed_payload_only_record_t; + +typedef struct +{ + par_type_t data; /**< Canonical parameter value. */ +} par_nvm_layout_fixed_payload_only_data_obj_t; + +typedef par_nvm_layout_fixed_payload_only_data_obj_t par_nvm_data_obj_t; +#elif (PAR_CFG_NVM_RECORD_LAYOUT == PAR_CFG_NVM_RECORD_LAYOUT_GROUPED_PAYLOAD_ONLY) +/** + * @brief Selected grouped payload-only layout. + * + * @note Only crc + active payload bytes are serialized. Persistent-slot order + * is regrouped into 8-bit, 16-bit, and 32-bit payload bands. + */ +typedef struct +{ + uint8_t crc; /**< CRC-8 over payload bytes. */ + uint8_t payload[PAR_NVM_RECORD_DATA_SLOT_SIZE]; /**< Maximum payload storage. */ +} par_nvm_layout_grouped_payload_only_record_t; + +typedef struct +{ + par_type_t data; /**< Canonical parameter value. */ +} par_nvm_layout_grouped_payload_only_data_obj_t; + +typedef par_nvm_layout_grouped_payload_only_data_obj_t par_nvm_data_obj_t; +#else +#error "Unsupported PAR_CFG_NVM_RECORD_LAYOUT selection." +#endif + +/** + * @brief Selected persisted-record layout vtable. + * + * @details Each concrete layout owns its own object preparation, serialized + * read/write path, compatibility decision, and optional readback-compare + * policy. `par_nvm.c` binds this table once during initialization and then + * operates only through these callbacks. + */ +typedef struct +{ + /** + * @brief Return the serialized byte size of one persisted record. + * + * @param par_num Live parameter number that owns the persistent slot. + * @return Serialized record size in bytes for the selected layout. + */ + uint32_t (*record_size_from_par_num)(const par_num_t par_num); + /** + * @brief Translate one persistent slot index into its serialized storage address. + * + * @param first_data_obj_addr Absolute start address of the first persisted record. + * @param persist_idx Compile-time persistent slot index. + * @param p_persist_slot_to_par_num Compile-time slot-to-parameter mapping table. + * @return Absolute address of the selected serialized record. + */ + uint32_t (*addr_from_persist_idx)(const uint32_t first_data_obj_addr, + const uint16_t persist_idx, + const par_num_t * const p_persist_slot_to_par_num); + /** + * @brief Populate one canonical NVM data object from the live RAM value. + * + * @param par_num Live parameter number. + * @param p_live_data Pointer to the live parameter value in canonical form. + * @param p_obj Output canonical NVM object prepared for the selected layout. + */ + void (*populate_data_obj)(const par_num_t par_num, + const par_type_t * const p_live_data, + par_nvm_data_obj_t * const p_obj); + /** + * @brief Read and validate one serialized record from the backend. + * + * @param p_store Active storage backend API. + * @param addr Absolute record address inside the managed NVM image. + * @param par_num Live parameter number associated with the slot. + * @param p_obj Output canonical NVM object loaded from storage. + * @return Operation status. Layout-level CRC and size validation are handled here. + */ + par_status_t (*read)(const par_store_backend_api_t * const p_store, + const uint32_t addr, + const par_num_t par_num, + par_nvm_data_obj_t * const p_obj); + /** + * @brief Serialize and write one canonical NVM object to storage. + * + * @param p_store Active storage backend API. + * @param addr Absolute record address inside the managed NVM image. + * @param par_num Live parameter number associated with the slot. + * @param p_obj Canonical NVM object to serialize and persist. + * @return Operation status. + */ + par_status_t (*write)(const par_store_backend_api_t * const p_store, + const uint32_t addr, + const par_num_t par_num, + const par_nvm_data_obj_t * const p_obj); + /** + * @brief Validate one already loaded canonical object against the live schema. + * + * @param par_num Live parameter number expected at this slot. + * @param p_obj Canonical NVM object that was read from storage. + * @param pp_reason Output short mismatch reason string for diagnostics. + * @param p_stored_id Output stored ID value when the layout carries one. + * @return `ePAR_OK` when the object matches the current live schema. + */ + par_status_t (*validate_loaded_obj)(const par_num_t par_num, + const par_nvm_data_obj_t * const p_obj, + const char ** const pp_reason, + uint16_t * const p_stored_id); + /** + * @brief Return the best stored-ID diagnostic value for an error path. + * + * @param par_num Live parameter number associated with the slot. + * @param p_obj Canonical NVM object that was read from storage. + * @return Stored ID for diagnostics, or a layout-defined fallback value. + */ + uint16_t (*get_error_stored_id)(const par_num_t par_num, + const par_nvm_data_obj_t * const p_obj); + /** + * @brief Decide whether the stored header remains layout-compatible. + * + * @param p_head_obj Validated NVM header loaded from storage. + * @return Layout-specific compatibility decision for rebuild/exact/prefix-append. + */ + par_nvm_compat_result_t (*check_compat)(const par_nvm_head_obj_t * const p_head_obj); +#if (1 == PAR_CFG_NVM_WRITE_VERIFY_EN) + /** + * @brief Compare an expected object against a read-back object. + * + * @param par_num Live parameter number associated with the slot. + * @param p_expected Canonical object prepared from the live value before write. + * @param p_actual Canonical object reloaded from storage after write. + * @return True when the write-readback comparison passes for this layout. + */ + bool (*data_obj_matches)(const par_num_t par_num, + const par_nvm_data_obj_t * const p_expected, + const par_nvm_data_obj_t * const p_actual); +#endif +} par_nvm_layout_api_t; + +uint8_t par_nvm_layout_payload_size_from_type(const par_type_list_t type); +uint8_t par_nvm_layout_payload_size_from_par_num(const par_num_t par_num); +void par_nvm_layout_pack_payload_bytes(const par_type_list_t type, + const par_type_t * const p_data, + uint8_t * const p_payload); +void par_nvm_layout_unpack_payload_bytes(const par_type_list_t type, + const uint8_t * const p_payload, + par_type_t * const p_data); + +uint8_t par_nvm_layout_calc_crc(const uint8_t size_desc, + const uint8_t * const p_payload, + const uint8_t payload_size, + const bool include_size_desc); +uint8_t par_nvm_layout_calc_crc_with_id(const uint16_t id, + const uint8_t size_desc, + const uint8_t * const p_payload, + const uint8_t payload_size, + const bool include_size_desc); + +/** + * @brief Bind and return the concrete layout-ops table selected at compile time. + * + * @return Non-null pointer to the selected layout adapter. + */ +const par_nvm_layout_api_t *par_nvm_layout_init(void); + +#endif /* 1 == PAR_CFG_NVM_EN */ + +#endif /* _PAR_NVM_LAYOUT_H_ */ diff --git a/src/persist/par_nvm_layout_compact_payload.c b/src/persist/par_nvm_layout_compact_payload.c new file mode 100644 index 0000000..0a1e183 --- /dev/null +++ b/src/persist/par_nvm_layout_compact_payload.c @@ -0,0 +1,381 @@ +/** + * @file par_nvm_layout_compact_payload.c + * @brief Implement the compact persisted-record layout with id, size, crc, and payload bytes. + * @author wdfk-prog + * @version 1.2 + * @date 2026-04-13 + * + * @copyright Copyright (c) 2026 Ziga Miklosic. Distributed under the MIT license. + * + * @note : + * @par Change Log: + * Date Version Author Description + * 2026-04-06 1.0 wdfk-prog first version + * 2026-04-13 1.1 wdfk-prog add layout-ops adapter + * 2026-04-13 1.2 wdfk-prog auto-generate compile-time compact-payload address LUT + */ +#include "persist/par_nvm_layout.h" + +#if (1 == PAR_CFG_NVM_EN) && (PAR_CFG_NVM_RECORD_LAYOUT == PAR_CFG_NVM_RECORD_LAYOUT_COMPACT_PAYLOAD) + +#include +#include + +#include "persist/par_nvm_table_id.h" + +#define PAR_NVM_LAYOUT_RECORD_OVERHEAD (PAR_NVM_RECORD_ID_SIZE + PAR_NVM_RECORD_SIZE_FIELD_SIZE + PAR_NVM_RECORD_CRC_SIZE) +#define PAR_NVM_LAYOUT_RECORD_MAX_SIZE (PAR_NVM_LAYOUT_RECORD_OVERHEAD + PAR_NVM_RECORD_DATA_SLOT_SIZE) + +PAR_STATIC_ASSERT(par_nvm_layout_compact_payload_record_payload_slot_is_4_bytes, + (sizeof(((par_nvm_layout_compact_payload_record_t *)0)->payload) == 4u)); + +/** + * @brief Return the serialized record size for one compact-payload record width. + * + * @param payload_size Natural payload width in bytes. + * @return Serialized record size in bytes. + */ +static uint32_t par_nvm_layout_compact_payload_record_size_from_payload_size(const uint8_t payload_size) +{ + return (PAR_NVM_LAYOUT_RECORD_OVERHEAD + (uint32_t)payload_size); +} + +/** + * @brief Return the serialized byte size of one persisted record for this layout. + * + * @param par_num Live parameter number associated with the slot. + * @return Serialized record size in bytes. + */ +static uint32_t par_nvm_layout_compact_payload_record_size_from_par_num(const par_num_t par_num) +{ + return par_nvm_layout_compact_payload_record_size_from_payload_size(par_nvm_layout_payload_size_from_par_num(par_num)); +} + +/** + * @brief Compile-time packed offset map for the compact-payload layout. + * + * @details The generated struct keeps one byte of anchor storage first so the + * type remains valid even when no parameters are persistent. Every persistent + * record then contributes one tightly packed `uint8_t[]` member whose size is + * derived from `par_table.def`. `offsetof()` on these members therefore yields + * the exact prefix-sum byte offset without any handwritten constants. + */ +#define PAR_NVM_LAYOUT_COMPACT_PAYLOAD_OFFSET_MAP_BASE_SIZE 1U +#define PAR_NVM_LAYOUT_COMPACT_PAYLOAD_RECORD_SIZE_BYTES(payload_size_) (PAR_NVM_LAYOUT_RECORD_OVERHEAD + (uint32_t)(payload_size_)) +#define PAR_NVM_LAYOUT_COMPACT_PAYLOAD_PERSIST_SELECT(enum_, pers_, emit_) PAR_NVM_LAYOUT_COMPACT_PAYLOAD_PERSIST_SELECT_I(enum_, pers_, emit_) +#define PAR_NVM_LAYOUT_COMPACT_PAYLOAD_PERSIST_SELECT_I(enum_, pers_, emit_) PAR_NVM_LAYOUT_COMPACT_PAYLOAD_PERSIST_SELECT_##pers_(enum_, emit_) +#define PAR_NVM_LAYOUT_COMPACT_PAYLOAD_PERSIST_SELECT_1(enum_, emit_) emit_(enum_) +#define PAR_NVM_LAYOUT_COMPACT_PAYLOAD_PERSIST_SELECT_0(enum_, emit_) +#define PAR_NVM_LAYOUT_COMPACT_PAYLOAD_MEMBER_1(enum_) uint8_t slot_##enum_[PAR_NVM_LAYOUT_COMPACT_PAYLOAD_RECORD_SIZE_BYTES(1U)]; +#define PAR_NVM_LAYOUT_COMPACT_PAYLOAD_MEMBER_2(enum_) uint8_t slot_##enum_[PAR_NVM_LAYOUT_COMPACT_PAYLOAD_RECORD_SIZE_BYTES(2U)]; +#define PAR_NVM_LAYOUT_COMPACT_PAYLOAD_MEMBER_4(enum_) uint8_t slot_##enum_[PAR_NVM_LAYOUT_COMPACT_PAYLOAD_RECORD_SIZE_BYTES(4U)]; +typedef struct +{ + uint8_t base__[PAR_NVM_LAYOUT_COMPACT_PAYLOAD_OFFSET_MAP_BASE_SIZE]; +#define PAR_ITEM_U8(...) PAR_NVM_LAYOUT_COMPACT_PAYLOAD_PERSIST_SELECT(PAR_XARG_ENUM(__VA_ARGS__), PAR_XARG_PERS(__VA_ARGS__), PAR_NVM_LAYOUT_COMPACT_PAYLOAD_MEMBER_1) +#define PAR_ITEM_U16(...) PAR_NVM_LAYOUT_COMPACT_PAYLOAD_PERSIST_SELECT(PAR_XARG_ENUM(__VA_ARGS__), PAR_XARG_PERS(__VA_ARGS__), PAR_NVM_LAYOUT_COMPACT_PAYLOAD_MEMBER_2) +#define PAR_ITEM_U32(...) PAR_NVM_LAYOUT_COMPACT_PAYLOAD_PERSIST_SELECT(PAR_XARG_ENUM(__VA_ARGS__), PAR_XARG_PERS(__VA_ARGS__), PAR_NVM_LAYOUT_COMPACT_PAYLOAD_MEMBER_4) +#define PAR_ITEM_I8(...) PAR_NVM_LAYOUT_COMPACT_PAYLOAD_PERSIST_SELECT(PAR_XARG_ENUM(__VA_ARGS__), PAR_XARG_PERS(__VA_ARGS__), PAR_NVM_LAYOUT_COMPACT_PAYLOAD_MEMBER_1) +#define PAR_ITEM_I16(...) PAR_NVM_LAYOUT_COMPACT_PAYLOAD_PERSIST_SELECT(PAR_XARG_ENUM(__VA_ARGS__), PAR_XARG_PERS(__VA_ARGS__), PAR_NVM_LAYOUT_COMPACT_PAYLOAD_MEMBER_2) +#define PAR_ITEM_I32(...) PAR_NVM_LAYOUT_COMPACT_PAYLOAD_PERSIST_SELECT(PAR_XARG_ENUM(__VA_ARGS__), PAR_XARG_PERS(__VA_ARGS__), PAR_NVM_LAYOUT_COMPACT_PAYLOAD_MEMBER_4) +#define PAR_ITEM_F32(...) PAR_NVM_LAYOUT_COMPACT_PAYLOAD_PERSIST_SELECT(PAR_XARG_ENUM(__VA_ARGS__), PAR_XARG_PERS(__VA_ARGS__), PAR_NVM_LAYOUT_COMPACT_PAYLOAD_MEMBER_4) +#include "../../par_table.def" +#undef PAR_ITEM_U8 +#undef PAR_ITEM_U16 +#undef PAR_ITEM_U32 +#undef PAR_ITEM_I8 +#undef PAR_ITEM_I16 +#undef PAR_ITEM_I32 +#undef PAR_ITEM_F32 +} par_nvm_layout_compact_payload_offset_map_t; +#define PAR_NVM_LAYOUT_COMPACT_PAYLOAD_OFFSET_OF(enum_) ((uint32_t)offsetof(par_nvm_layout_compact_payload_offset_map_t, slot_##enum_) - PAR_NVM_LAYOUT_COMPACT_PAYLOAD_OFFSET_MAP_BASE_SIZE) +#define PAR_NVM_LAYOUT_COMPACT_PAYLOAD_ADDR_ENTRY_SELECT(enum_, pers_) PAR_NVM_LAYOUT_COMPACT_PAYLOAD_ADDR_ENTRY_SELECT_I(enum_, pers_) +#define PAR_NVM_LAYOUT_COMPACT_PAYLOAD_ADDR_ENTRY_SELECT_I(enum_, pers_) PAR_NVM_LAYOUT_COMPACT_PAYLOAD_ADDR_ENTRY_SELECT_##pers_(enum_) +#define PAR_NVM_LAYOUT_COMPACT_PAYLOAD_ADDR_ENTRY_SELECT_1(enum_) [PAR_PERSIST_IDX_##enum_] = PAR_NVM_LAYOUT_COMPACT_PAYLOAD_OFFSET_OF(enum_), +#define PAR_NVM_LAYOUT_COMPACT_PAYLOAD_ADDR_ENTRY_SELECT_0(enum_) +static const uint32_t g_par_nvm_layout_compact_payload_addr_lut[PAR_PERSIST_SLOT_MAP_CAPACITY] = { +#define PAR_ITEM_U8(...) PAR_NVM_LAYOUT_COMPACT_PAYLOAD_ADDR_ENTRY_SELECT(PAR_XARG_ENUM(__VA_ARGS__), PAR_XARG_PERS(__VA_ARGS__)) +#define PAR_ITEM_U16(...) PAR_NVM_LAYOUT_COMPACT_PAYLOAD_ADDR_ENTRY_SELECT(PAR_XARG_ENUM(__VA_ARGS__), PAR_XARG_PERS(__VA_ARGS__)) +#define PAR_ITEM_U32(...) PAR_NVM_LAYOUT_COMPACT_PAYLOAD_ADDR_ENTRY_SELECT(PAR_XARG_ENUM(__VA_ARGS__), PAR_XARG_PERS(__VA_ARGS__)) +#define PAR_ITEM_I8(...) PAR_NVM_LAYOUT_COMPACT_PAYLOAD_ADDR_ENTRY_SELECT(PAR_XARG_ENUM(__VA_ARGS__), PAR_XARG_PERS(__VA_ARGS__)) +#define PAR_ITEM_I16(...) PAR_NVM_LAYOUT_COMPACT_PAYLOAD_ADDR_ENTRY_SELECT(PAR_XARG_ENUM(__VA_ARGS__), PAR_XARG_PERS(__VA_ARGS__)) +#define PAR_ITEM_I32(...) PAR_NVM_LAYOUT_COMPACT_PAYLOAD_ADDR_ENTRY_SELECT(PAR_XARG_ENUM(__VA_ARGS__), PAR_XARG_PERS(__VA_ARGS__)) +#define PAR_ITEM_F32(...) PAR_NVM_LAYOUT_COMPACT_PAYLOAD_ADDR_ENTRY_SELECT(PAR_XARG_ENUM(__VA_ARGS__), PAR_XARG_PERS(__VA_ARGS__)) +#include "../../par_table.def" +#undef PAR_ITEM_U8 +#undef PAR_ITEM_U16 +#undef PAR_ITEM_U32 +#undef PAR_ITEM_I8 +#undef PAR_ITEM_I16 +#undef PAR_ITEM_I32 +#undef PAR_ITEM_F32 +}; +#undef PAR_NVM_LAYOUT_COMPACT_PAYLOAD_ADDR_ENTRY_SELECT +#undef PAR_NVM_LAYOUT_COMPACT_PAYLOAD_ADDR_ENTRY_SELECT_I +#undef PAR_NVM_LAYOUT_COMPACT_PAYLOAD_ADDR_ENTRY_SELECT_1 +#undef PAR_NVM_LAYOUT_COMPACT_PAYLOAD_ADDR_ENTRY_SELECT_0 +#undef PAR_NVM_LAYOUT_COMPACT_PAYLOAD_OFFSET_OF +#undef PAR_NVM_LAYOUT_COMPACT_PAYLOAD_MEMBER_1 +#undef PAR_NVM_LAYOUT_COMPACT_PAYLOAD_MEMBER_2 +#undef PAR_NVM_LAYOUT_COMPACT_PAYLOAD_MEMBER_4 +#undef PAR_NVM_LAYOUT_COMPACT_PAYLOAD_PERSIST_SELECT +#undef PAR_NVM_LAYOUT_COMPACT_PAYLOAD_PERSIST_SELECT_I +#undef PAR_NVM_LAYOUT_COMPACT_PAYLOAD_PERSIST_SELECT_1 +#undef PAR_NVM_LAYOUT_COMPACT_PAYLOAD_PERSIST_SELECT_0 +#undef PAR_NVM_LAYOUT_COMPACT_PAYLOAD_RECORD_SIZE_BYTES + +/** + * @brief Translate one persistent slot index into its serialized record address. + * + * @param first_data_obj_addr Absolute address of the first persisted record. + * @param persist_idx Compile-time persistent slot index. + * @param p_persist_slot_to_par_num Compile-time slot-to-parameter mapping table. + * @return Absolute address of the selected record. + */ +static uint32_t par_nvm_layout_compact_payload_addr_from_persist_idx(const uint32_t first_data_obj_addr, + const uint16_t persist_idx, + const par_num_t * const p_persist_slot_to_par_num) +{ + PAR_ASSERT(NULL != p_persist_slot_to_par_num); + PAR_ASSERT(persist_idx < PAR_PERSISTENT_COMPILE_COUNT); + (void)p_persist_slot_to_par_num; + + return (first_data_obj_addr + g_par_nvm_layout_compact_payload_addr_lut[persist_idx]); +} + +/** + * @brief Populate one canonical NVM object from the live parameter value. + * + * @param par_num Live parameter number. + * @param p_live_data Pointer to the live canonical parameter value. + * @param p_obj Output canonical NVM object. + */ +static void par_nvm_layout_compact_payload_populate_data_obj(const par_num_t par_num, + const par_type_t * const p_live_data, + par_nvm_data_obj_t * const p_obj) +{ + PAR_ASSERT((NULL != p_live_data) && (NULL != p_obj)); + + memset(p_obj, 0, sizeof(*p_obj)); + p_obj->id = par_cfg_get_param_id_const(par_num); + p_obj->size = par_nvm_layout_payload_size_from_par_num(par_num); + p_obj->data = *p_live_data; +} + +/** + * @brief Read and validate one serialized record from storage. + * + * @param p_store Active storage backend API. + * @param addr Absolute record address inside the managed NVM image. + * @param par_num Live parameter number associated with the slot. + * @param p_obj Output canonical NVM object. + * @return Operation status. + */ +static par_status_t par_nvm_layout_compact_payload_read(const par_store_backend_api_t * const p_store, + const uint32_t addr, + const par_num_t par_num, + par_nvm_data_obj_t * const p_obj) +{ + uint8_t record_buf[PAR_NVM_LAYOUT_RECORD_MAX_SIZE] = { 0U }; + const par_cfg_t * const p_cfg = par_get_config(par_num); + const uint8_t expected_payload_size = par_nvm_layout_payload_size_from_par_num(par_num); + const uint32_t record_size = par_nvm_layout_compact_payload_record_size_from_payload_size(expected_payload_size); + uint8_t size_desc = 0U; + const uint8_t * const p_payload = &record_buf[PAR_NVM_RECORD_ID_SIZE + PAR_NVM_RECORD_SIZE_FIELD_SIZE + PAR_NVM_RECORD_CRC_SIZE]; + uint8_t crc_calc = 0U; + + PAR_ASSERT((NULL != p_store) && (NULL != p_obj)); + PAR_ASSERT(NULL != p_cfg); + memset(p_obj, 0, sizeof(*p_obj)); + + if (ePAR_OK != p_store->read(addr, record_size, record_buf)) + { + return ePAR_ERROR_NVM; + } + + memcpy(&p_obj->id, &record_buf[0], sizeof(p_obj->id)); + size_desc = record_buf[PAR_NVM_RECORD_ID_SIZE]; + if (size_desc != expected_payload_size) + { + return ePAR_ERROR; + } + + crc_calc = par_nvm_layout_calc_crc_with_id(p_obj->id, size_desc, p_payload, size_desc, true); + if (crc_calc != record_buf[PAR_NVM_RECORD_ID_SIZE + PAR_NVM_RECORD_SIZE_FIELD_SIZE]) + { + return ePAR_ERROR_CRC; + } + + p_obj->size = size_desc; + par_nvm_layout_unpack_payload_bytes(p_cfg->type, p_payload, &p_obj->data); + return ePAR_OK; +} + +/** + * @brief Serialize and write one canonical NVM object to storage. + * + * @param p_store Active storage backend API. + * @param addr Absolute record address inside the managed NVM image. + * @param par_num Live parameter number associated with the slot. + * @param p_obj Canonical NVM object to serialize. + * @return Operation status. + */ +static par_status_t par_nvm_layout_compact_payload_write(const par_store_backend_api_t * const p_store, + const uint32_t addr, + const par_num_t par_num, + const par_nvm_data_obj_t * const p_obj) +{ + uint8_t record_buf[PAR_NVM_LAYOUT_RECORD_MAX_SIZE] = { 0U }; + const par_cfg_t * const p_cfg = par_get_config(par_num); + const uint8_t payload_size = par_nvm_layout_payload_size_from_par_num(par_num); + const uint32_t record_size = par_nvm_layout_compact_payload_record_size_from_payload_size(payload_size); + uint8_t * const p_payload = &record_buf[PAR_NVM_RECORD_ID_SIZE + PAR_NVM_RECORD_SIZE_FIELD_SIZE + PAR_NVM_RECORD_CRC_SIZE]; + uint8_t crc = 0U; + + PAR_ASSERT((NULL != p_store) && (NULL != p_obj)); + PAR_ASSERT(NULL != p_cfg); + + par_nvm_layout_pack_payload_bytes(p_cfg->type, &p_obj->data, p_payload); + crc = par_nvm_layout_calc_crc_with_id(p_obj->id, payload_size, p_payload, payload_size, true); + + memcpy(&record_buf[0], &p_obj->id, sizeof(p_obj->id)); + record_buf[PAR_NVM_RECORD_ID_SIZE] = payload_size; + record_buf[PAR_NVM_RECORD_ID_SIZE + PAR_NVM_RECORD_SIZE_FIELD_SIZE] = crc; + + return (ePAR_OK == p_store->write(addr, record_size, record_buf)) ? ePAR_OK : ePAR_ERROR_NVM; +} + +/** + * @brief Validate one loaded canonical object against the current live schema. + * + * @param par_num Live parameter number expected at this slot. + * @param p_obj Canonical object loaded from NVM. + * @param pp_reason Output short mismatch reason for diagnostics. + * @param p_stored_id Output stored ID value when the layout carries one. + * @return Operation status. + */ +static par_status_t par_nvm_layout_compact_payload_validate_loaded_obj(const par_num_t par_num, + const par_nvm_data_obj_t * const p_obj, + const char ** const pp_reason, + uint16_t * const p_stored_id) +{ + const uint16_t expected_id = par_cfg_get_param_id_const(par_num); + const uint8_t expected_payload_size = par_nvm_layout_payload_size_from_par_num(par_num); + + PAR_ASSERT((NULL != p_obj) && (NULL != pp_reason) && (NULL != p_stored_id)); + + *pp_reason = NULL; + *p_stored_id = p_obj->id; + + if (p_obj->id != expected_id) + { + *pp_reason = "id-mismatch"; + return ePAR_ERROR; + } + + if (p_obj->size != expected_payload_size) + { + *pp_reason = "size-mismatch"; + return ePAR_ERROR; + } + + return ePAR_OK; +} + +/** + * @brief Return the stored-ID diagnostic value for an error path. + * + * @param par_num Live parameter number associated with the slot. + * @param p_obj Canonical object loaded from NVM. + * @return Stored ID or a layout-defined fallback value. + */ +static uint16_t par_nvm_layout_compact_payload_get_error_stored_id(const par_num_t par_num, + const par_nvm_data_obj_t * const p_obj) +{ + (void)par_num; + return (NULL != p_obj) ? p_obj->id : 0U; +} + +/** + * @brief Decide whether the stored header remains compatible with this layout. + * + * @param p_head_obj Validated NVM header object. + * @return Layout-specific compatibility decision. + */ +static par_nvm_compat_result_t par_nvm_layout_compact_payload_check_compat(const par_nvm_head_obj_t * const p_head_obj) +{ + PAR_ASSERT(NULL != p_head_obj); + + if (p_head_obj->obj_nb > (uint16_t)PAR_PERSISTENT_COMPILE_COUNT) + { + return ePAR_NVM_COMPAT_REBUILD; + } + + if (p_head_obj->table_id != par_nvm_table_id_calc_for_count(p_head_obj->obj_nb)) + { + return ePAR_NVM_COMPAT_REBUILD; + } + + return (p_head_obj->obj_nb == (uint16_t)PAR_PERSISTENT_COMPILE_COUNT) ? ePAR_NVM_COMPAT_EXACT_MATCH : ePAR_NVM_COMPAT_PREFIX_APPEND; +} + +#if (1 == PAR_CFG_NVM_WRITE_VERIFY_EN) +/** + * @brief Compare an expected object against a post-write read-back object. + * + * @param par_num Live parameter number associated with the slot. + * @param p_expected Expected canonical NVM object. + * @param p_actual Canonical object reloaded from storage. + * @return True when both objects match under this layout policy. + */ +static bool par_nvm_layout_compact_payload_data_obj_matches(const par_num_t par_num, + const par_nvm_data_obj_t * const p_expected, + const par_nvm_data_obj_t * const p_actual) +{ + const par_cfg_t * const p_cfg = par_get_config(par_num); + const uint8_t payload_size = par_nvm_layout_payload_size_from_par_num(par_num); + uint8_t expected_payload[PAR_NVM_RECORD_DATA_SLOT_SIZE] = { 0U }; + uint8_t actual_payload[PAR_NVM_RECORD_DATA_SLOT_SIZE] = { 0U }; + + PAR_ASSERT((NULL != p_cfg) && (NULL != p_expected) && (NULL != p_actual)); + + if ((p_expected->id != p_actual->id) || + (p_actual->size != payload_size)) + { + return false; + } + + par_nvm_layout_pack_payload_bytes(p_cfg->type, &p_expected->data, expected_payload); + par_nvm_layout_pack_payload_bytes(p_cfg->type, &p_actual->data, actual_payload); + return (0 == memcmp(expected_payload, actual_payload, payload_size)); +} +#endif + +/** + * @brief Concrete layout adapter bound by the common NVM core. + */ +static const par_nvm_layout_api_t g_par_nvm_layout_api = { + .record_size_from_par_num = par_nvm_layout_compact_payload_record_size_from_par_num, + .addr_from_persist_idx = par_nvm_layout_compact_payload_addr_from_persist_idx, + .populate_data_obj = par_nvm_layout_compact_payload_populate_data_obj, + .read = par_nvm_layout_compact_payload_read, + .write = par_nvm_layout_compact_payload_write, + .validate_loaded_obj = par_nvm_layout_compact_payload_validate_loaded_obj, + .get_error_stored_id = par_nvm_layout_compact_payload_get_error_stored_id, + .check_compat = par_nvm_layout_compact_payload_check_compat, +#if (1 == PAR_CFG_NVM_WRITE_VERIFY_EN) + .data_obj_matches = par_nvm_layout_compact_payload_data_obj_matches, +#endif +}; + +/** + * @brief Return the concrete layout adapter selected for this build. + * + * @return Non-null pointer to this layout adapter. + */ +const par_nvm_layout_api_t *par_nvm_layout_init(void) +{ + return &g_par_nvm_layout_api; +} + +#endif /* compact-payload */ diff --git a/src/persist/par_nvm_layout_fixed_payload_only.c b/src/persist/par_nvm_layout_fixed_payload_only.c new file mode 100644 index 0000000..bd109af --- /dev/null +++ b/src/persist/par_nvm_layout_fixed_payload_only.c @@ -0,0 +1,346 @@ +/** + * @file par_nvm_layout_fixed_payload_only.c + * @brief Implement the fixed persistent-order payload-only NVM layout. + * @author wdfk-prog + * @version 1.3 + * @date 2026-04-13 + * + * @copyright Copyright (c) 2026 Ziga Miklosic. Distributed under the MIT license. + * + * @note : + * @par Change Log: + * Date Version Author Description + * 2026-04-10 1.0 wdfk-prog first version + * 2026-04-11 1.1 wdfk-prog restore layout comments and split layout structs + * 2026-04-13 1.2 wdfk-prog add layout-ops adapter + * 2026-04-13 1.3 wdfk-prog auto-generate compile-time fixed-payload-only address LUT + */ +#include "persist/par_nvm_layout.h" + +#if (1 == PAR_CFG_NVM_EN) && (PAR_CFG_NVM_RECORD_LAYOUT == PAR_CFG_NVM_RECORD_LAYOUT_FIXED_PAYLOAD_ONLY) + +#include +#include + +#include "persist/par_nvm_table_id.h" + +#define PAR_NVM_LAYOUT_RECORD_OVERHEAD ((uint32_t)PAR_NVM_RECORD_CRC_SIZE) +#define PAR_NVM_LAYOUT_RECORD_MAX_SIZE (PAR_NVM_LAYOUT_RECORD_OVERHEAD + PAR_NVM_RECORD_DATA_SLOT_SIZE) + +PAR_STATIC_ASSERT(par_nvm_layout_fixed_payload_only_record_payload_slot_is_4_bytes, + (sizeof(((par_nvm_layout_fixed_payload_only_record_t *)0)->payload) == 4u)); + +/** + * @brief Return the serialized record size for one payload-only record width. + * + * @param payload_size Natural payload width in bytes. + * @return Serialized record size in bytes. + */ +static uint32_t par_nvm_layout_fixed_payload_only_record_size_from_payload_size(const uint8_t payload_size) +{ + return (PAR_NVM_LAYOUT_RECORD_OVERHEAD + (uint32_t)payload_size); +} + +/** + * @brief Return the serialized byte size of one persisted record for this layout. + * + * @param par_num Live parameter number associated with the slot. + * @return Serialized record size in bytes. + */ +static uint32_t par_nvm_layout_fixed_payload_only_record_size_from_par_num(const par_num_t par_num) +{ + return par_nvm_layout_fixed_payload_only_record_size_from_payload_size(par_nvm_layout_payload_size_from_par_num(par_num)); +} + +/** + * @brief Compile-time packed offset map for the fixed-payload-only layout. + * + * @details The generated struct keeps one anchor byte so the type remains valid + * even when no parameters are persistent. Every persistent record then expands + * into one tightly packed `uint8_t[]` member sized from `par_table.def`, and + * `offsetof()` yields the exact persistent prefix sum without handwritten + * offsets. + */ +#define PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_OFFSET_MAP_BASE_SIZE 1U +#define PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_RECORD_SIZE_BYTES(payload_size_) (PAR_NVM_LAYOUT_RECORD_OVERHEAD + (uint32_t)(payload_size_)) +#define PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_PERSIST_SELECT(enum_, pers_, emit_) PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_PERSIST_SELECT_I(enum_, pers_, emit_) +#define PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_PERSIST_SELECT_I(enum_, pers_, emit_) PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_PERSIST_SELECT_##pers_(enum_, emit_) +#define PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_PERSIST_SELECT_1(enum_, emit_) emit_(enum_) +#define PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_PERSIST_SELECT_0(enum_, emit_) +#define PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_MEMBER_1(enum_) uint8_t slot_##enum_[PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_RECORD_SIZE_BYTES(1U)]; +#define PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_MEMBER_2(enum_) uint8_t slot_##enum_[PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_RECORD_SIZE_BYTES(2U)]; +#define PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_MEMBER_4(enum_) uint8_t slot_##enum_[PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_RECORD_SIZE_BYTES(4U)]; +typedef struct +{ + uint8_t base__[PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_OFFSET_MAP_BASE_SIZE]; +#define PAR_ITEM_U8(...) PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_PERSIST_SELECT(PAR_XARG_ENUM(__VA_ARGS__), PAR_XARG_PERS(__VA_ARGS__), PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_MEMBER_1) +#define PAR_ITEM_U16(...) PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_PERSIST_SELECT(PAR_XARG_ENUM(__VA_ARGS__), PAR_XARG_PERS(__VA_ARGS__), PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_MEMBER_2) +#define PAR_ITEM_U32(...) PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_PERSIST_SELECT(PAR_XARG_ENUM(__VA_ARGS__), PAR_XARG_PERS(__VA_ARGS__), PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_MEMBER_4) +#define PAR_ITEM_I8(...) PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_PERSIST_SELECT(PAR_XARG_ENUM(__VA_ARGS__), PAR_XARG_PERS(__VA_ARGS__), PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_MEMBER_1) +#define PAR_ITEM_I16(...) PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_PERSIST_SELECT(PAR_XARG_ENUM(__VA_ARGS__), PAR_XARG_PERS(__VA_ARGS__), PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_MEMBER_2) +#define PAR_ITEM_I32(...) PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_PERSIST_SELECT(PAR_XARG_ENUM(__VA_ARGS__), PAR_XARG_PERS(__VA_ARGS__), PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_MEMBER_4) +#define PAR_ITEM_F32(...) PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_PERSIST_SELECT(PAR_XARG_ENUM(__VA_ARGS__), PAR_XARG_PERS(__VA_ARGS__), PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_MEMBER_4) +#include "../../par_table.def" +#undef PAR_ITEM_U8 +#undef PAR_ITEM_U16 +#undef PAR_ITEM_U32 +#undef PAR_ITEM_I8 +#undef PAR_ITEM_I16 +#undef PAR_ITEM_I32 +#undef PAR_ITEM_F32 +} par_nvm_layout_fixed_payload_only_offset_map_t; +#define PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_OFFSET_OF(enum_) ((uint32_t)offsetof(par_nvm_layout_fixed_payload_only_offset_map_t, slot_##enum_) - PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_OFFSET_MAP_BASE_SIZE) +#define PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_ADDR_ENTRY_SELECT(enum_, pers_) PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_ADDR_ENTRY_SELECT_I(enum_, pers_) +#define PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_ADDR_ENTRY_SELECT_I(enum_, pers_) PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_ADDR_ENTRY_SELECT_##pers_(enum_) +#define PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_ADDR_ENTRY_SELECT_1(enum_) [PAR_PERSIST_IDX_##enum_] = PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_OFFSET_OF(enum_), +#define PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_ADDR_ENTRY_SELECT_0(enum_) +static const uint32_t g_par_nvm_layout_fixed_payload_only_addr_lut[PAR_PERSIST_SLOT_MAP_CAPACITY] = { +#define PAR_ITEM_U8(...) PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_ADDR_ENTRY_SELECT(PAR_XARG_ENUM(__VA_ARGS__), PAR_XARG_PERS(__VA_ARGS__)) +#define PAR_ITEM_U16(...) PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_ADDR_ENTRY_SELECT(PAR_XARG_ENUM(__VA_ARGS__), PAR_XARG_PERS(__VA_ARGS__)) +#define PAR_ITEM_U32(...) PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_ADDR_ENTRY_SELECT(PAR_XARG_ENUM(__VA_ARGS__), PAR_XARG_PERS(__VA_ARGS__)) +#define PAR_ITEM_I8(...) PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_ADDR_ENTRY_SELECT(PAR_XARG_ENUM(__VA_ARGS__), PAR_XARG_PERS(__VA_ARGS__)) +#define PAR_ITEM_I16(...) PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_ADDR_ENTRY_SELECT(PAR_XARG_ENUM(__VA_ARGS__), PAR_XARG_PERS(__VA_ARGS__)) +#define PAR_ITEM_I32(...) PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_ADDR_ENTRY_SELECT(PAR_XARG_ENUM(__VA_ARGS__), PAR_XARG_PERS(__VA_ARGS__)) +#define PAR_ITEM_F32(...) PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_ADDR_ENTRY_SELECT(PAR_XARG_ENUM(__VA_ARGS__), PAR_XARG_PERS(__VA_ARGS__)) +#include "../../par_table.def" +#undef PAR_ITEM_U8 +#undef PAR_ITEM_U16 +#undef PAR_ITEM_U32 +#undef PAR_ITEM_I8 +#undef PAR_ITEM_I16 +#undef PAR_ITEM_I32 +#undef PAR_ITEM_F32 +}; +#undef PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_ADDR_ENTRY_SELECT +#undef PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_ADDR_ENTRY_SELECT_I +#undef PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_ADDR_ENTRY_SELECT_1 +#undef PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_ADDR_ENTRY_SELECT_0 +#undef PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_OFFSET_OF +#undef PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_MEMBER_1 +#undef PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_MEMBER_2 +#undef PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_MEMBER_4 +#undef PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_PERSIST_SELECT +#undef PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_PERSIST_SELECT_I +#undef PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_PERSIST_SELECT_1 +#undef PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_PERSIST_SELECT_0 +#undef PAR_NVM_LAYOUT_FIXED_PAYLOAD_ONLY_RECORD_SIZE_BYTES + +/** + * @brief Translate one persistent slot index into its serialized record address. + * + * @param first_data_obj_addr Absolute address of the first persisted record. + * @param persist_idx Compile-time persistent slot index. + * @param p_persist_slot_to_par_num Compile-time slot-to-parameter mapping table. + * @return Absolute address of the selected record. + */ +static uint32_t par_nvm_layout_fixed_payload_only_addr_from_persist_idx(const uint32_t first_data_obj_addr, + const uint16_t persist_idx, + const par_num_t * const p_persist_slot_to_par_num) +{ + PAR_ASSERT(NULL != p_persist_slot_to_par_num); + PAR_ASSERT(persist_idx < PAR_PERSISTENT_COMPILE_COUNT); + (void)p_persist_slot_to_par_num; + + return (first_data_obj_addr + g_par_nvm_layout_fixed_payload_only_addr_lut[persist_idx]); +} + +/** + * @brief Populate one canonical NVM object from the live parameter value. + * + * @param par_num Live parameter number. + * @param p_live_data Pointer to the live canonical parameter value. + * @param p_obj Output canonical NVM object. + */ +static void par_nvm_layout_fixed_payload_only_populate_data_obj(const par_num_t par_num, + const par_type_t * const p_live_data, + par_nvm_data_obj_t * const p_obj) +{ + (void)par_num; + PAR_ASSERT((NULL != p_live_data) && (NULL != p_obj)); + + memset(p_obj, 0, sizeof(*p_obj)); + p_obj->data = *p_live_data; +} + +/** + * @brief Read and validate one serialized record from storage. + * + * @param p_store Active storage backend API. + * @param addr Absolute record address inside the managed NVM image. + * @param par_num Live parameter number associated with the slot. + * @param p_obj Output canonical NVM object. + * @return Operation status. + */ +static par_status_t par_nvm_layout_fixed_payload_only_read(const par_store_backend_api_t * const p_store, + const uint32_t addr, + const par_num_t par_num, + par_nvm_data_obj_t * const p_obj) +{ + uint8_t record_buf[PAR_NVM_LAYOUT_RECORD_MAX_SIZE] = { 0U }; + const par_cfg_t * const p_cfg = par_get_config(par_num); + const uint8_t payload_size = par_nvm_layout_payload_size_from_par_num(par_num); + const uint32_t record_size = par_nvm_layout_fixed_payload_only_record_size_from_payload_size(payload_size); + const uint8_t * const p_payload = &record_buf[PAR_NVM_RECORD_CRC_SIZE]; + uint8_t crc_calc = 0U; + + PAR_ASSERT((NULL != p_store) && (NULL != p_obj)); + PAR_ASSERT(NULL != p_cfg); + memset(p_obj, 0, sizeof(*p_obj)); + + if (ePAR_OK != p_store->read(addr, record_size, record_buf)) + { + return ePAR_ERROR_NVM; + } + + crc_calc = par_nvm_layout_calc_crc(0U, p_payload, payload_size, false); + if (crc_calc != record_buf[0]) + { + return ePAR_ERROR_CRC; + } + + par_nvm_layout_unpack_payload_bytes(p_cfg->type, p_payload, &p_obj->data); + return ePAR_OK; +} + +/** + * @brief Serialize and write one canonical NVM object to storage. + * + * @param p_store Active storage backend API. + * @param addr Absolute record address inside the managed NVM image. + * @param par_num Live parameter number associated with the slot. + * @param p_obj Canonical NVM object to serialize. + * @return Operation status. + */ +static par_status_t par_nvm_layout_fixed_payload_only_write(const par_store_backend_api_t * const p_store, + const uint32_t addr, + const par_num_t par_num, + const par_nvm_data_obj_t * const p_obj) +{ + uint8_t record_buf[PAR_NVM_LAYOUT_RECORD_MAX_SIZE] = { 0U }; + const par_cfg_t * const p_cfg = par_get_config(par_num); + const uint8_t payload_size = par_nvm_layout_payload_size_from_par_num(par_num); + const uint32_t record_size = par_nvm_layout_fixed_payload_only_record_size_from_payload_size(payload_size); + uint8_t * const p_payload = &record_buf[PAR_NVM_RECORD_CRC_SIZE]; + + PAR_ASSERT((NULL != p_store) && (NULL != p_obj)); + PAR_ASSERT(NULL != p_cfg); + + par_nvm_layout_pack_payload_bytes(p_cfg->type, &p_obj->data, p_payload); + record_buf[0] = par_nvm_layout_calc_crc(0U, p_payload, payload_size, false); + + return (ePAR_OK == p_store->write(addr, record_size, record_buf)) ? ePAR_OK : ePAR_ERROR_NVM; +} + +/** + * @brief Validate one loaded canonical object against the current live schema. + * + * @param par_num Live parameter number expected at this slot. + * @param p_obj Canonical object loaded from NVM. + * @param pp_reason Output short mismatch reason for diagnostics. + * @param p_stored_id Output stored ID value when the layout carries one. + * @return Operation status. + */ +static par_status_t par_nvm_layout_fixed_payload_only_validate_loaded_obj(const par_num_t par_num, + const par_nvm_data_obj_t * const p_obj, + const char ** const pp_reason, + uint16_t * const p_stored_id) +{ + PAR_ASSERT((NULL != p_obj) && (NULL != pp_reason) && (NULL != p_stored_id)); + + (void)p_obj; + *pp_reason = NULL; + *p_stored_id = par_cfg_get_param_id_const(par_num); + return ePAR_OK; +} + +/** + * @brief Return the stored-ID diagnostic value for an error path. + * + * @param par_num Live parameter number associated with the slot. + * @param p_obj Canonical object loaded from NVM. + * @return Stored ID or a layout-defined fallback value. + */ +static uint16_t par_nvm_layout_fixed_payload_only_get_error_stored_id(const par_num_t par_num, + const par_nvm_data_obj_t * const p_obj) +{ + (void)p_obj; + return par_cfg_get_param_id_const(par_num); +} + +/** + * @brief Decide whether the stored header remains compatible with this layout. + * + * @param p_head_obj Validated NVM header object. + * @return Layout-specific compatibility decision. + */ +static par_nvm_compat_result_t par_nvm_layout_fixed_payload_only_check_compat(const par_nvm_head_obj_t * const p_head_obj) +{ + PAR_ASSERT(NULL != p_head_obj); + + if (p_head_obj->obj_nb > (uint16_t)PAR_PERSISTENT_COMPILE_COUNT) + { + return ePAR_NVM_COMPAT_REBUILD; + } + + if (p_head_obj->table_id != par_nvm_table_id_calc_for_count(p_head_obj->obj_nb)) + { + return ePAR_NVM_COMPAT_REBUILD; + } + + return (p_head_obj->obj_nb == (uint16_t)PAR_PERSISTENT_COMPILE_COUNT) ? ePAR_NVM_COMPAT_EXACT_MATCH : ePAR_NVM_COMPAT_PREFIX_APPEND; +} + +#if (1 == PAR_CFG_NVM_WRITE_VERIFY_EN) +/** + * @brief Compare an expected object against a post-write read-back object. + * + * @param par_num Live parameter number associated with the slot. + * @param p_expected Expected canonical NVM object. + * @param p_actual Canonical object reloaded from storage. + * @return True when both objects match under this layout policy. + */ +static bool par_nvm_layout_fixed_payload_only_data_obj_matches(const par_num_t par_num, + const par_nvm_data_obj_t * const p_expected, + const par_nvm_data_obj_t * const p_actual) +{ + const par_cfg_t * const p_cfg = par_get_config(par_num); + const uint8_t payload_size = par_nvm_layout_payload_size_from_par_num(par_num); + uint8_t expected_payload[PAR_NVM_RECORD_DATA_SLOT_SIZE] = { 0U }; + uint8_t actual_payload[PAR_NVM_RECORD_DATA_SLOT_SIZE] = { 0U }; + + PAR_ASSERT((NULL != p_cfg) && (NULL != p_expected) && (NULL != p_actual)); + + par_nvm_layout_pack_payload_bytes(p_cfg->type, &p_expected->data, expected_payload); + par_nvm_layout_pack_payload_bytes(p_cfg->type, &p_actual->data, actual_payload); + return (0 == memcmp(expected_payload, actual_payload, payload_size)); +} +#endif + +/** + * @brief Concrete layout adapter bound by the common NVM core. + */ +static const par_nvm_layout_api_t g_par_nvm_layout_api = { + .record_size_from_par_num = par_nvm_layout_fixed_payload_only_record_size_from_par_num, + .addr_from_persist_idx = par_nvm_layout_fixed_payload_only_addr_from_persist_idx, + .populate_data_obj = par_nvm_layout_fixed_payload_only_populate_data_obj, + .read = par_nvm_layout_fixed_payload_only_read, + .write = par_nvm_layout_fixed_payload_only_write, + .validate_loaded_obj = par_nvm_layout_fixed_payload_only_validate_loaded_obj, + .get_error_stored_id = par_nvm_layout_fixed_payload_only_get_error_stored_id, + .check_compat = par_nvm_layout_fixed_payload_only_check_compat, +#if (1 == PAR_CFG_NVM_WRITE_VERIFY_EN) + .data_obj_matches = par_nvm_layout_fixed_payload_only_data_obj_matches, +#endif +}; + +/** + * @brief Return the concrete layout adapter selected for this build. + * + * @return Non-null pointer to this layout adapter. + */ +const par_nvm_layout_api_t *par_nvm_layout_init(void) +{ + return &g_par_nvm_layout_api; +} + +#endif /* fixed-payload-only */ diff --git a/src/persist/par_nvm_layout_fixed_slot_no_size.c b/src/persist/par_nvm_layout_fixed_slot_no_size.c new file mode 100644 index 0000000..fa36f97 --- /dev/null +++ b/src/persist/par_nvm_layout_fixed_slot_no_size.c @@ -0,0 +1,279 @@ +/** + * @file par_nvm_layout_fixed_slot_no_size.c + * @brief Implement the fixed-slot persisted-record layout without a size descriptor. + * @author wdfk-prog + * @version 1.1 + * @date 2026-04-13 + * + * @copyright Copyright (c) 2026 Ziga Miklosic. Distributed under the MIT license. + * + * @note : + * @par Change Log: + * Date Version Author Description + * 2026-04-06 1.0 wdfk-prog first version + * 2026-04-13 1.1 wdfk-prog add layout-ops adapter + */ +#include "persist/par_nvm_layout.h" + +#if (1 == PAR_CFG_NVM_EN) && (PAR_CFG_NVM_RECORD_LAYOUT == PAR_CFG_NVM_RECORD_LAYOUT_FIXED_SLOT_NO_SIZE) + +#include + +#include "persist/par_nvm_table_id.h" + +#define PAR_NVM_LAYOUT_RECORD_SIZE (PAR_NVM_RECORD_ID_SIZE + PAR_NVM_RECORD_CRC_SIZE + (uint32_t)PAR_NVM_RECORD_DATA_SLOT_SIZE) + +PAR_STATIC_ASSERT(par_nvm_layout_fixed_no_size_payload_slot_is_4_bytes, + (sizeof(((par_nvm_layout_fixed_slot_no_size_record_t *)0)->payload) == 4u)); + +/** + * @brief Return the serialized byte size of one persisted record for this layout. + * + * @param par_num Live parameter number associated with the slot. + * @return Serialized record size in bytes. + */ +static uint32_t par_nvm_layout_fixed_slot_no_size_record_size_from_par_num(const par_num_t par_num) +{ + (void)par_num; + return PAR_NVM_LAYOUT_RECORD_SIZE; +} + +/** + * @brief Translate one persistent slot index into its serialized record address. + * + * @param first_data_obj_addr Absolute address of the first persisted record. + * @param persist_idx Compile-time persistent slot index. + * @param p_persist_slot_to_par_num Compile-time slot-to-parameter mapping table. + * @return Absolute address of the selected record. + */ +static uint32_t par_nvm_layout_fixed_slot_no_size_addr_from_persist_idx(const uint32_t first_data_obj_addr, + const uint16_t persist_idx, + const par_num_t * const p_persist_slot_to_par_num) +{ + (void)p_persist_slot_to_par_num; + return (first_data_obj_addr + ((uint32_t)persist_idx * PAR_NVM_LAYOUT_RECORD_SIZE)); +} + +/** + * @brief Populate one canonical NVM object from the live parameter value. + * + * @param par_num Live parameter number. + * @param p_live_data Pointer to the live canonical parameter value. + * @param p_obj Output canonical NVM object. + */ +static void par_nvm_layout_fixed_slot_no_size_populate_data_obj(const par_num_t par_num, + const par_type_t * const p_live_data, + par_nvm_data_obj_t * const p_obj) +{ + PAR_ASSERT((NULL != p_live_data) && (NULL != p_obj)); + + memset(p_obj, 0, sizeof(*p_obj)); + p_obj->id = par_cfg_get_param_id_const(par_num); + p_obj->data = *p_live_data; +} + +/** + * @brief Read and validate one serialized record from storage. + * + * @param p_store Active storage backend API. + * @param addr Absolute record address inside the managed NVM image. + * @param par_num Live parameter number associated with the slot. + * @param p_obj Output canonical NVM object. + * @return Operation status. + */ +static par_status_t par_nvm_layout_fixed_slot_no_size_read(const par_store_backend_api_t * const p_store, + const uint32_t addr, + const par_num_t par_num, + par_nvm_data_obj_t * const p_obj) +{ + uint8_t record_buf[PAR_NVM_LAYOUT_RECORD_SIZE] = { 0U }; + par_type_t payload_raw = { 0U }; + uint8_t crc_calc = 0U; + + (void)par_num; + PAR_ASSERT((NULL != p_store) && (NULL != p_obj)); + memset(p_obj, 0, sizeof(*p_obj)); + + if (ePAR_OK != p_store->read(addr, PAR_NVM_LAYOUT_RECORD_SIZE, record_buf)) + { + return ePAR_ERROR_NVM; + } + + memcpy(&p_obj->id, &record_buf[0], sizeof(p_obj->id)); + memcpy(&payload_raw, &record_buf[PAR_NVM_RECORD_ID_SIZE + PAR_NVM_RECORD_CRC_SIZE], sizeof(payload_raw)); + + crc_calc = par_nvm_layout_calc_crc_with_id(p_obj->id, + 0U, + (const uint8_t * const)&payload_raw, + PAR_NVM_RECORD_DATA_SLOT_SIZE, + false); + if (crc_calc != record_buf[PAR_NVM_RECORD_ID_SIZE]) + { + return ePAR_ERROR_CRC; + } + + p_obj->data = payload_raw; + return ePAR_OK; +} + +/** + * @brief Serialize and write one canonical NVM object to storage. + * + * @param p_store Active storage backend API. + * @param addr Absolute record address inside the managed NVM image. + * @param par_num Live parameter number associated with the slot. + * @param p_obj Canonical NVM object to serialize. + * @return Operation status. + */ +static par_status_t par_nvm_layout_fixed_slot_no_size_write(const par_store_backend_api_t * const p_store, + const uint32_t addr, + const par_num_t par_num, + const par_nvm_data_obj_t * const p_obj) +{ + uint8_t record_buf[PAR_NVM_LAYOUT_RECORD_SIZE] = { 0U }; + par_type_t payload_raw = p_obj->data; + uint8_t crc = 0U; + + (void)par_num; + PAR_ASSERT((NULL != p_store) && (NULL != p_obj)); + + crc = par_nvm_layout_calc_crc_with_id(p_obj->id, + 0U, + (const uint8_t * const)&payload_raw, + PAR_NVM_RECORD_DATA_SLOT_SIZE, + false); + + memcpy(&record_buf[0], &p_obj->id, sizeof(p_obj->id)); + record_buf[PAR_NVM_RECORD_ID_SIZE] = crc; + memcpy(&record_buf[PAR_NVM_RECORD_ID_SIZE + PAR_NVM_RECORD_CRC_SIZE], &payload_raw, sizeof(payload_raw)); + + return (ePAR_OK == p_store->write(addr, PAR_NVM_LAYOUT_RECORD_SIZE, record_buf)) ? ePAR_OK : ePAR_ERROR_NVM; +} + +/** + * @brief Validate one loaded canonical object against the current live schema. + * + * @param par_num Live parameter number expected at this slot. + * @param p_obj Canonical object loaded from NVM. + * @param pp_reason Output short mismatch reason for diagnostics. + * @param p_stored_id Output stored ID value when the layout carries one. + * @return Operation status. + */ +static par_status_t par_nvm_layout_fixed_slot_no_size_validate_loaded_obj(const par_num_t par_num, + const par_nvm_data_obj_t * const p_obj, + const char ** const pp_reason, + uint16_t * const p_stored_id) +{ + const uint16_t expected_id = par_cfg_get_param_id_const(par_num); + + PAR_ASSERT((NULL != p_obj) && (NULL != pp_reason) && (NULL != p_stored_id)); + + *pp_reason = NULL; + *p_stored_id = p_obj->id; + + if (p_obj->id != expected_id) + { + *pp_reason = "id-mismatch"; + return ePAR_ERROR; + } + + return ePAR_OK; +} + +/** + * @brief Return the stored-ID diagnostic value for an error path. + * + * @param par_num Live parameter number associated with the slot. + * @param p_obj Canonical object loaded from NVM. + * @return Stored ID or a layout-defined fallback value. + */ +static uint16_t par_nvm_layout_fixed_slot_no_size_get_error_stored_id(const par_num_t par_num, + const par_nvm_data_obj_t * const p_obj) +{ + (void)par_num; + return (NULL != p_obj) ? p_obj->id : 0U; +} + +/** + * @brief Decide whether the stored header remains compatible with this layout. + * + * @param p_head_obj Validated NVM header object. + * @return Layout-specific compatibility decision. + */ +static par_nvm_compat_result_t par_nvm_layout_fixed_slot_no_size_check_compat(const par_nvm_head_obj_t * const p_head_obj) +{ + PAR_ASSERT(NULL != p_head_obj); + + if (p_head_obj->obj_nb > (uint16_t)PAR_PERSISTENT_COMPILE_COUNT) + { + return ePAR_NVM_COMPAT_REBUILD; + } + + if (p_head_obj->table_id != par_nvm_table_id_calc_for_count(p_head_obj->obj_nb)) + { + return ePAR_NVM_COMPAT_REBUILD; + } + + return (p_head_obj->obj_nb == (uint16_t)PAR_PERSISTENT_COMPILE_COUNT) ? + ePAR_NVM_COMPAT_EXACT_MATCH : + ePAR_NVM_COMPAT_PREFIX_APPEND; +} + +#if (1 == PAR_CFG_NVM_WRITE_VERIFY_EN) +/** + * @brief Compare an expected object against a post-write read-back object. + * + * @param par_num Live parameter number associated with the slot. + * @param p_expected Expected canonical NVM object. + * @param p_actual Canonical object reloaded from storage. + * @return True when both objects match under this layout policy. + */ +static bool par_nvm_layout_fixed_slot_no_size_data_obj_matches(const par_num_t par_num, + const par_nvm_data_obj_t * const p_expected, + const par_nvm_data_obj_t * const p_actual) +{ + uint8_t expected_payload[PAR_NVM_RECORD_DATA_SLOT_SIZE] = { 0U }; + uint8_t actual_payload[PAR_NVM_RECORD_DATA_SLOT_SIZE] = { 0U }; + + (void)par_num; + PAR_ASSERT((NULL != p_expected) && (NULL != p_actual)); + + if (p_expected->id != p_actual->id) + { + return false; + } + + memcpy(expected_payload, &p_expected->data, sizeof(p_expected->data)); + memcpy(actual_payload, &p_actual->data, sizeof(p_actual->data)); + return (0 == memcmp(expected_payload, actual_payload, PAR_NVM_RECORD_DATA_SLOT_SIZE)); +} +#endif + +/** + * @brief Concrete layout adapter bound by the common NVM core. + */ +static const par_nvm_layout_api_t g_par_nvm_layout_api = { + .record_size_from_par_num = par_nvm_layout_fixed_slot_no_size_record_size_from_par_num, + .addr_from_persist_idx = par_nvm_layout_fixed_slot_no_size_addr_from_persist_idx, + .populate_data_obj = par_nvm_layout_fixed_slot_no_size_populate_data_obj, + .read = par_nvm_layout_fixed_slot_no_size_read, + .write = par_nvm_layout_fixed_slot_no_size_write, + .validate_loaded_obj = par_nvm_layout_fixed_slot_no_size_validate_loaded_obj, + .get_error_stored_id = par_nvm_layout_fixed_slot_no_size_get_error_stored_id, + .check_compat = par_nvm_layout_fixed_slot_no_size_check_compat, +#if (1 == PAR_CFG_NVM_WRITE_VERIFY_EN) + .data_obj_matches = par_nvm_layout_fixed_slot_no_size_data_obj_matches, +#endif +}; + +/** + * @brief Return the concrete layout adapter selected for this build. + * + * @return Non-null pointer to this layout adapter. + */ +const par_nvm_layout_api_t *par_nvm_layout_init(void) +{ + return &g_par_nvm_layout_api; +} + +#endif /* fixed-slot-no-size */ diff --git a/src/persist/par_nvm_layout_fixed_slot_with_size.c b/src/persist/par_nvm_layout_fixed_slot_with_size.c new file mode 100644 index 0000000..68dc989 --- /dev/null +++ b/src/persist/par_nvm_layout_fixed_slot_with_size.c @@ -0,0 +1,290 @@ +/** + * @file par_nvm_layout_fixed_slot_with_size.c + * @brief Implement the fixed-slot persisted-record layout with a size descriptor. + * @author wdfk-prog + * @version 1.1 + * @date 2026-04-13 + * + * @copyright Copyright (c) 2026 Ziga Miklosic. Distributed under the MIT license. + * + * @note : + * @par Change Log: + * Date Version Author Description + * 2026-04-06 1.0 wdfk-prog first version + * 2026-04-13 1.1 wdfk-prog add layout-ops adapter + */ +#include "persist/par_nvm_layout.h" + +#if (1 == PAR_CFG_NVM_EN) && (PAR_CFG_NVM_RECORD_LAYOUT == PAR_CFG_NVM_RECORD_LAYOUT_FIXED_SLOT_WITH_SIZE) + +#include + +#include "persist/par_nvm_table_id.h" + +/** + * @brief Serialized size of one fixed-slot record with an explicit size field. + */ +#define PAR_NVM_LAYOUT_RECORD_SIZE ((uint32_t)sizeof(par_nvm_layout_fixed_slot_with_size_record_t)) + +PAR_STATIC_ASSERT(par_nvm_layout_fixed_with_size_record_is_8_bytes, + (sizeof(par_nvm_layout_fixed_slot_with_size_record_t) == 8u)); + +/** + * @brief Return the serialized byte size of one persisted record for this layout. + * + * @param par_num Live parameter number associated with the slot. + * @return Serialized record size in bytes. + */ +static uint32_t par_nvm_layout_fixed_slot_with_size_record_size_from_par_num(const par_num_t par_num) +{ + (void)par_num; + return PAR_NVM_LAYOUT_RECORD_SIZE; +} + +/** + * @brief Translate one persistent slot index into its serialized record address. + * + * @param first_data_obj_addr Absolute address of the first persisted record. + * @param persist_idx Compile-time persistent slot index. + * @param p_persist_slot_to_par_num Compile-time slot-to-parameter mapping table. + * @return Absolute address of the selected record. + */ +static uint32_t par_nvm_layout_fixed_slot_with_size_addr_from_persist_idx(const uint32_t first_data_obj_addr, + const uint16_t persist_idx, + const par_num_t * const p_persist_slot_to_par_num) +{ + (void)p_persist_slot_to_par_num; + return (first_data_obj_addr + ((uint32_t)persist_idx * PAR_NVM_LAYOUT_RECORD_SIZE)); +} + +/** + * @brief Populate one canonical NVM object from the live parameter value. + * + * @param par_num Live parameter number. + * @param p_live_data Pointer to the live canonical parameter value. + * @param p_obj Output canonical NVM object. + */ +static void par_nvm_layout_fixed_slot_with_size_populate_data_obj(const par_num_t par_num, + const par_type_t * const p_live_data, + par_nvm_data_obj_t * const p_obj) +{ + PAR_ASSERT((NULL != p_live_data) && (NULL != p_obj)); + + memset(p_obj, 0, sizeof(*p_obj)); + p_obj->id = par_cfg_get_param_id_const(par_num); + p_obj->size = PAR_NVM_RECORD_DATA_SLOT_SIZE; + p_obj->data = *p_live_data; +} + +/** + * @brief Read and validate one serialized record from storage. + * + * @param p_store Active storage backend API. + * @param addr Absolute record address inside the managed NVM image. + * @param par_num Live parameter number associated with the slot. + * @param p_obj Output canonical NVM object. + * @return Operation status. + */ +static par_status_t par_nvm_layout_fixed_slot_with_size_read(const par_store_backend_api_t * const p_store, + const uint32_t addr, + const par_num_t par_num, + par_nvm_data_obj_t * const p_obj) +{ + par_nvm_layout_fixed_slot_with_size_record_t record = { 0U }; + uint8_t crc_calc = 0U; + + (void)par_num; + PAR_ASSERT((NULL != p_store) && (NULL != p_obj)); + memset(p_obj, 0, sizeof(*p_obj)); + + if (ePAR_OK != p_store->read(addr, PAR_NVM_LAYOUT_RECORD_SIZE, (uint8_t *)&record)) + { + return ePAR_ERROR_NVM; + } + + if (record.size != PAR_NVM_RECORD_DATA_SLOT_SIZE) + { + return ePAR_ERROR; + } + + crc_calc = par_nvm_layout_calc_crc_with_id(record.id, + record.size, + (const uint8_t * const)&record.data, + PAR_NVM_RECORD_DATA_SLOT_SIZE, + true); + if (crc_calc != record.crc) + { + return ePAR_ERROR_CRC; + } + + p_obj->id = record.id; + p_obj->size = record.size; + p_obj->data = record.data; + return ePAR_OK; +} + +/** + * @brief Serialize and write one canonical NVM object to storage. + * + * @param p_store Active storage backend API. + * @param addr Absolute record address inside the managed NVM image. + * @param par_num Live parameter number associated with the slot. + * @param p_obj Canonical NVM object to serialize. + * @return Operation status. + */ +static par_status_t par_nvm_layout_fixed_slot_with_size_write(const par_store_backend_api_t * const p_store, + const uint32_t addr, + const par_num_t par_num, + const par_nvm_data_obj_t * const p_obj) +{ + par_nvm_layout_fixed_slot_with_size_record_t record = { 0U }; + + (void)par_num; + PAR_ASSERT((NULL != p_store) && (NULL != p_obj)); + + record.id = p_obj->id; + record.size = PAR_NVM_RECORD_DATA_SLOT_SIZE; + record.data = p_obj->data; + record.crc = par_nvm_layout_calc_crc_with_id(record.id, + record.size, + (const uint8_t * const)&record.data, + PAR_NVM_RECORD_DATA_SLOT_SIZE, + true); + + return (ePAR_OK == p_store->write(addr, PAR_NVM_LAYOUT_RECORD_SIZE, (const uint8_t *)&record)) ? ePAR_OK : ePAR_ERROR_NVM; +} + +/** + * @brief Validate one loaded canonical object against the current live schema. + * + * @param par_num Live parameter number expected at this slot. + * @param p_obj Canonical object loaded from NVM. + * @param pp_reason Output short mismatch reason for diagnostics. + * @param p_stored_id Output stored ID value when the layout carries one. + * @return Operation status. + */ +static par_status_t par_nvm_layout_fixed_slot_with_size_validate_loaded_obj(const par_num_t par_num, + const par_nvm_data_obj_t * const p_obj, + const char ** const pp_reason, + uint16_t * const p_stored_id) +{ + const uint16_t expected_id = par_cfg_get_param_id_const(par_num); + + PAR_ASSERT((NULL != p_obj) && (NULL != pp_reason) && (NULL != p_stored_id)); + + *pp_reason = NULL; + *p_stored_id = p_obj->id; + + if (p_obj->id != expected_id) + { + *pp_reason = "id-mismatch"; + return ePAR_ERROR; + } + + if (p_obj->size != PAR_NVM_RECORD_DATA_SLOT_SIZE) + { + *pp_reason = "size-mismatch"; + return ePAR_ERROR; + } + + return ePAR_OK; +} + +/** + * @brief Return the stored-ID diagnostic value for an error path. + * + * @param par_num Live parameter number associated with the slot. + * @param p_obj Canonical object loaded from NVM. + * @return Stored ID or a layout-defined fallback value. + */ +static uint16_t par_nvm_layout_fixed_slot_with_size_get_error_stored_id(const par_num_t par_num, + const par_nvm_data_obj_t * const p_obj) +{ + (void)par_num; + return (NULL != p_obj) ? p_obj->id : 0U; +} + +/** + * @brief Decide whether the stored header remains compatible with this layout. + * + * @param p_head_obj Validated NVM header object. + * @return Layout-specific compatibility decision. + */ +static par_nvm_compat_result_t par_nvm_layout_fixed_slot_with_size_check_compat(const par_nvm_head_obj_t * const p_head_obj) +{ + PAR_ASSERT(NULL != p_head_obj); + + if (p_head_obj->obj_nb > (uint16_t)PAR_PERSISTENT_COMPILE_COUNT) + { + return ePAR_NVM_COMPAT_REBUILD; + } + + if (p_head_obj->table_id != par_nvm_table_id_calc_for_count(p_head_obj->obj_nb)) + { + return ePAR_NVM_COMPAT_REBUILD; + } + + return (p_head_obj->obj_nb == (uint16_t)PAR_PERSISTENT_COMPILE_COUNT) ? + ePAR_NVM_COMPAT_EXACT_MATCH : + ePAR_NVM_COMPAT_PREFIX_APPEND; +} + +#if (1 == PAR_CFG_NVM_WRITE_VERIFY_EN) +/** + * @brief Compare an expected object against a post-write read-back object. + * + * @param par_num Live parameter number associated with the slot. + * @param p_expected Expected canonical NVM object. + * @param p_actual Canonical object reloaded from storage. + * @return True when both objects match under this layout policy. + */ +static bool par_nvm_layout_fixed_slot_with_size_data_obj_matches(const par_num_t par_num, + const par_nvm_data_obj_t * const p_expected, + const par_nvm_data_obj_t * const p_actual) +{ + uint8_t expected_payload[PAR_NVM_RECORD_DATA_SLOT_SIZE] = { 0U }; + uint8_t actual_payload[PAR_NVM_RECORD_DATA_SLOT_SIZE] = { 0U }; + + (void)par_num; + PAR_ASSERT((NULL != p_expected) && (NULL != p_actual)); + + if ((p_expected->id != p_actual->id) || + (p_actual->size != PAR_NVM_RECORD_DATA_SLOT_SIZE)) + { + return false; + } + + memcpy(expected_payload, &p_expected->data, sizeof(p_expected->data)); + memcpy(actual_payload, &p_actual->data, sizeof(p_actual->data)); + return (0 == memcmp(expected_payload, actual_payload, PAR_NVM_RECORD_DATA_SLOT_SIZE)); +} +#endif + +/** + * @brief Concrete layout adapter bound by the common NVM core. + */ +static const par_nvm_layout_api_t g_par_nvm_layout_api = { + .record_size_from_par_num = par_nvm_layout_fixed_slot_with_size_record_size_from_par_num, + .addr_from_persist_idx = par_nvm_layout_fixed_slot_with_size_addr_from_persist_idx, + .populate_data_obj = par_nvm_layout_fixed_slot_with_size_populate_data_obj, + .read = par_nvm_layout_fixed_slot_with_size_read, + .write = par_nvm_layout_fixed_slot_with_size_write, + .validate_loaded_obj = par_nvm_layout_fixed_slot_with_size_validate_loaded_obj, + .get_error_stored_id = par_nvm_layout_fixed_slot_with_size_get_error_stored_id, + .check_compat = par_nvm_layout_fixed_slot_with_size_check_compat, +#if (1 == PAR_CFG_NVM_WRITE_VERIFY_EN) + .data_obj_matches = par_nvm_layout_fixed_slot_with_size_data_obj_matches, +#endif +}; + +/** + * @brief Return the concrete layout adapter selected for this build. + * + * @return Non-null pointer to this layout adapter. + */ +const par_nvm_layout_api_t *par_nvm_layout_init(void) +{ + return &g_par_nvm_layout_api; +} + +#endif /* fixed-slot-with-size */ diff --git a/src/persist/par_nvm_layout_grouped_payload_only.c b/src/persist/par_nvm_layout_grouped_payload_only.c new file mode 100644 index 0000000..6a329a6 --- /dev/null +++ b/src/persist/par_nvm_layout_grouped_payload_only.c @@ -0,0 +1,375 @@ +/** + * @file par_nvm_layout_grouped_payload_only.c + * @brief Implement the grouped payload-only NVM layout. + * @author wdfk-prog + * @version 1.2 + * @date 2026-04-13 + * + * @copyright Copyright (c) 2026 Ziga Miklosic. Distributed under the MIT license. + * + * @note : + * @par Change Log: + * Date Version Author Description + * 2026-04-11 1.0 wdfk-prog first version + * 2026-04-13 1.1 wdfk-prog add layout-ops adapter + * 2026-04-13 1.2 wdfk-prog auto-generate compile-time grouped-payload-only address LUT + */ +#include "persist/par_nvm_layout.h" + +#if (1 == PAR_CFG_NVM_EN) && (PAR_CFG_NVM_RECORD_LAYOUT == PAR_CFG_NVM_RECORD_LAYOUT_GROUPED_PAYLOAD_ONLY) + +#include +#include + +#include "persist/par_nvm_table_id.h" + +#define PAR_NVM_LAYOUT_RECORD_OVERHEAD ((uint32_t)PAR_NVM_RECORD_CRC_SIZE) +#define PAR_NVM_LAYOUT_RECORD_MAX_SIZE (PAR_NVM_LAYOUT_RECORD_OVERHEAD + PAR_NVM_RECORD_DATA_SLOT_SIZE) + +PAR_STATIC_ASSERT(par_nvm_layout_grouped_payload_only_record_payload_slot_is_4_bytes, + (sizeof(((par_nvm_layout_grouped_payload_only_record_t *)0)->payload) == 4u)); + +/** + * @brief Return the serialized record size for one grouped payload-only record width. + * + * @param payload_size Natural payload width in bytes. + * @return Serialized record size in bytes. + */ +static uint32_t par_nvm_layout_grouped_payload_only_record_size_from_payload_size(const uint8_t payload_size) +{ + return (PAR_NVM_LAYOUT_RECORD_OVERHEAD + (uint32_t)payload_size); +} + +/** + * @brief Return the serialized byte size of one persisted record for this layout. + * + * @param par_num Live parameter number associated with the slot. + * @return Serialized record size in bytes. + */ +static uint32_t par_nvm_layout_grouped_payload_only_record_size_from_par_num(const par_num_t par_num) +{ + return par_nvm_layout_grouped_payload_only_record_size_from_payload_size(par_nvm_layout_payload_size_from_par_num(par_num)); +} + +/** + * @brief Compile-time packed offset map for the grouped-payload-only layout. + * + * @details The generated struct keeps one anchor byte so the type remains valid + * even when no parameters are persistent. Persistent items are then emitted in + * three compile-time passes: 8-bit payloads first, then 16-bit payloads, then + * 32-bit payloads. `offsetof()` on the generated members therefore yields the + * exact grouped-layout address without runtime scans or handwritten offsets. + */ +#define PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_OFFSET_MAP_BASE_SIZE 1U +#define PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_RECORD_SIZE_BYTES(payload_size_) (PAR_NVM_LAYOUT_RECORD_OVERHEAD + (uint32_t)(payload_size_)) +#define PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_PERSIST_SELECT(enum_, pers_, emit_) PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_PERSIST_SELECT_I(enum_, pers_, emit_) +#define PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_PERSIST_SELECT_I(enum_, pers_, emit_) PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_PERSIST_SELECT_##pers_(enum_, emit_) +#define PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_PERSIST_SELECT_1(enum_, emit_) emit_(enum_) +#define PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_PERSIST_SELECT_0(enum_, emit_) +#define PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_MEMBER_1(enum_) uint8_t slot_##enum_[PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_RECORD_SIZE_BYTES(1U)]; +#define PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_MEMBER_2(enum_) uint8_t slot_##enum_[PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_RECORD_SIZE_BYTES(2U)]; +#define PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_MEMBER_4(enum_) uint8_t slot_##enum_[PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_RECORD_SIZE_BYTES(4U)]; +typedef struct +{ + uint8_t base__[PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_OFFSET_MAP_BASE_SIZE]; +#define PAR_ITEM_U8(...) PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_PERSIST_SELECT(PAR_XARG_ENUM(__VA_ARGS__), PAR_XARG_PERS(__VA_ARGS__), PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_MEMBER_1) +#define PAR_ITEM_U16(...) +#define PAR_ITEM_U32(...) +#define PAR_ITEM_I8(...) PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_PERSIST_SELECT(PAR_XARG_ENUM(__VA_ARGS__), PAR_XARG_PERS(__VA_ARGS__), PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_MEMBER_1) +#define PAR_ITEM_I16(...) +#define PAR_ITEM_I32(...) +#define PAR_ITEM_F32(...) +#include "../../par_table.def" +#undef PAR_ITEM_U8 +#undef PAR_ITEM_U16 +#undef PAR_ITEM_U32 +#undef PAR_ITEM_I8 +#undef PAR_ITEM_I16 +#undef PAR_ITEM_I32 +#undef PAR_ITEM_F32 +#define PAR_ITEM_U8(...) +#define PAR_ITEM_U16(...) PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_PERSIST_SELECT(PAR_XARG_ENUM(__VA_ARGS__), PAR_XARG_PERS(__VA_ARGS__), PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_MEMBER_2) +#define PAR_ITEM_U32(...) +#define PAR_ITEM_I8(...) +#define PAR_ITEM_I16(...) PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_PERSIST_SELECT(PAR_XARG_ENUM(__VA_ARGS__), PAR_XARG_PERS(__VA_ARGS__), PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_MEMBER_2) +#define PAR_ITEM_I32(...) +#define PAR_ITEM_F32(...) +#include "../../par_table.def" +#undef PAR_ITEM_U8 +#undef PAR_ITEM_U16 +#undef PAR_ITEM_U32 +#undef PAR_ITEM_I8 +#undef PAR_ITEM_I16 +#undef PAR_ITEM_I32 +#undef PAR_ITEM_F32 +#define PAR_ITEM_U8(...) +#define PAR_ITEM_U16(...) +#define PAR_ITEM_U32(...) PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_PERSIST_SELECT(PAR_XARG_ENUM(__VA_ARGS__), PAR_XARG_PERS(__VA_ARGS__), PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_MEMBER_4) +#define PAR_ITEM_I8(...) +#define PAR_ITEM_I16(...) +#define PAR_ITEM_I32(...) PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_PERSIST_SELECT(PAR_XARG_ENUM(__VA_ARGS__), PAR_XARG_PERS(__VA_ARGS__), PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_MEMBER_4) +#define PAR_ITEM_F32(...) PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_PERSIST_SELECT(PAR_XARG_ENUM(__VA_ARGS__), PAR_XARG_PERS(__VA_ARGS__), PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_MEMBER_4) +#include "../../par_table.def" +#undef PAR_ITEM_U8 +#undef PAR_ITEM_U16 +#undef PAR_ITEM_U32 +#undef PAR_ITEM_I8 +#undef PAR_ITEM_I16 +#undef PAR_ITEM_I32 +#undef PAR_ITEM_F32 +} par_nvm_layout_grouped_payload_only_offset_map_t; +#define PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_OFFSET_OF(enum_) ((uint32_t)offsetof(par_nvm_layout_grouped_payload_only_offset_map_t, slot_##enum_) - PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_OFFSET_MAP_BASE_SIZE) +#define PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_ADDR_ENTRY_SELECT(enum_, pers_) PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_ADDR_ENTRY_SELECT_I(enum_, pers_) +#define PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_ADDR_ENTRY_SELECT_I(enum_, pers_) PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_ADDR_ENTRY_SELECT_##pers_(enum_) +#define PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_ADDR_ENTRY_SELECT_1(enum_) [PAR_PERSIST_IDX_##enum_] = PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_OFFSET_OF(enum_), +#define PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_ADDR_ENTRY_SELECT_0(enum_) +static const uint32_t g_par_nvm_layout_grouped_payload_only_addr_lut[PAR_PERSIST_SLOT_MAP_CAPACITY] = { +#define PAR_ITEM_U8(...) PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_ADDR_ENTRY_SELECT(PAR_XARG_ENUM(__VA_ARGS__), PAR_XARG_PERS(__VA_ARGS__)) +#define PAR_ITEM_U16(...) PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_ADDR_ENTRY_SELECT(PAR_XARG_ENUM(__VA_ARGS__), PAR_XARG_PERS(__VA_ARGS__)) +#define PAR_ITEM_U32(...) PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_ADDR_ENTRY_SELECT(PAR_XARG_ENUM(__VA_ARGS__), PAR_XARG_PERS(__VA_ARGS__)) +#define PAR_ITEM_I8(...) PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_ADDR_ENTRY_SELECT(PAR_XARG_ENUM(__VA_ARGS__), PAR_XARG_PERS(__VA_ARGS__)) +#define PAR_ITEM_I16(...) PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_ADDR_ENTRY_SELECT(PAR_XARG_ENUM(__VA_ARGS__), PAR_XARG_PERS(__VA_ARGS__)) +#define PAR_ITEM_I32(...) PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_ADDR_ENTRY_SELECT(PAR_XARG_ENUM(__VA_ARGS__), PAR_XARG_PERS(__VA_ARGS__)) +#define PAR_ITEM_F32(...) PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_ADDR_ENTRY_SELECT(PAR_XARG_ENUM(__VA_ARGS__), PAR_XARG_PERS(__VA_ARGS__)) +#include "../../par_table.def" +#undef PAR_ITEM_U8 +#undef PAR_ITEM_U16 +#undef PAR_ITEM_U32 +#undef PAR_ITEM_I8 +#undef PAR_ITEM_I16 +#undef PAR_ITEM_I32 +#undef PAR_ITEM_F32 +}; +#undef PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_ADDR_ENTRY_SELECT +#undef PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_ADDR_ENTRY_SELECT_I +#undef PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_ADDR_ENTRY_SELECT_1 +#undef PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_ADDR_ENTRY_SELECT_0 +#undef PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_OFFSET_OF +#undef PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_MEMBER_1 +#undef PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_MEMBER_2 +#undef PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_MEMBER_4 +#undef PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_PERSIST_SELECT +#undef PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_PERSIST_SELECT_I +#undef PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_PERSIST_SELECT_1 +#undef PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_PERSIST_SELECT_0 +#undef PAR_NVM_LAYOUT_GROUPED_PAYLOAD_ONLY_RECORD_SIZE_BYTES + +/** + * @brief Translate one persistent slot index into its serialized record address. + * + * @param first_data_obj_addr Absolute address of the first persisted record. + * @param persist_idx Compile-time persistent slot index. + * @param p_persist_slot_to_par_num Compile-time slot-to-parameter mapping table. + * @return Absolute address of the selected record. + */ +static uint32_t par_nvm_layout_grouped_payload_only_addr_from_persist_idx(const uint32_t first_data_obj_addr, + const uint16_t persist_idx, + const par_num_t * const p_persist_slot_to_par_num) +{ + PAR_ASSERT(NULL != p_persist_slot_to_par_num); + PAR_ASSERT(persist_idx < PAR_PERSISTENT_COMPILE_COUNT); + (void)p_persist_slot_to_par_num; + + return (first_data_obj_addr + g_par_nvm_layout_grouped_payload_only_addr_lut[persist_idx]); +} + +/** + * @brief Populate one canonical NVM object from the live parameter value. + * + * @param par_num Live parameter number. + * @param p_live_data Pointer to the live canonical parameter value. + * @param p_obj Output canonical NVM object. + */ +static void par_nvm_layout_grouped_payload_only_populate_data_obj(const par_num_t par_num, + const par_type_t * const p_live_data, + par_nvm_data_obj_t * const p_obj) +{ + (void)par_num; + PAR_ASSERT((NULL != p_live_data) && (NULL != p_obj)); + + memset(p_obj, 0, sizeof(*p_obj)); + p_obj->data = *p_live_data; +} + +/** + * @brief Read and validate one serialized record from storage. + * + * @param p_store Active storage backend API. + * @param addr Absolute record address inside the managed NVM image. + * @param par_num Live parameter number associated with the slot. + * @param p_obj Output canonical NVM object. + * @return Operation status. + */ +static par_status_t par_nvm_layout_grouped_payload_only_read(const par_store_backend_api_t * const p_store, + const uint32_t addr, + const par_num_t par_num, + par_nvm_data_obj_t * const p_obj) +{ + uint8_t record_buf[PAR_NVM_LAYOUT_RECORD_MAX_SIZE] = { 0U }; + const par_cfg_t * const p_cfg = par_get_config(par_num); + const uint8_t payload_size = par_nvm_layout_payload_size_from_par_num(par_num); + const uint32_t record_size = par_nvm_layout_grouped_payload_only_record_size_from_payload_size(payload_size); + const uint8_t * const p_payload = &record_buf[PAR_NVM_RECORD_CRC_SIZE]; + uint8_t crc_calc = 0U; + + PAR_ASSERT((NULL != p_store) && (NULL != p_obj)); + PAR_ASSERT(NULL != p_cfg); + memset(p_obj, 0, sizeof(*p_obj)); + + if (ePAR_OK != p_store->read(addr, record_size, record_buf)) + { + return ePAR_ERROR_NVM; + } + + crc_calc = par_nvm_layout_calc_crc(0U, p_payload, payload_size, false); + if (crc_calc != record_buf[0]) + { + return ePAR_ERROR_CRC; + } + + par_nvm_layout_unpack_payload_bytes(p_cfg->type, p_payload, &p_obj->data); + return ePAR_OK; +} + +/** + * @brief Serialize and write one canonical NVM object to storage. + * + * @param p_store Active storage backend API. + * @param addr Absolute record address inside the managed NVM image. + * @param par_num Live parameter number associated with the slot. + * @param p_obj Canonical NVM object to serialize. + * @return Operation status. + */ +static par_status_t par_nvm_layout_grouped_payload_only_write(const par_store_backend_api_t * const p_store, + const uint32_t addr, + const par_num_t par_num, + const par_nvm_data_obj_t * const p_obj) +{ + uint8_t record_buf[PAR_NVM_LAYOUT_RECORD_MAX_SIZE] = { 0U }; + const par_cfg_t * const p_cfg = par_get_config(par_num); + const uint8_t payload_size = par_nvm_layout_payload_size_from_par_num(par_num); + const uint32_t record_size = par_nvm_layout_grouped_payload_only_record_size_from_payload_size(payload_size); + uint8_t * const p_payload = &record_buf[PAR_NVM_RECORD_CRC_SIZE]; + + PAR_ASSERT((NULL != p_store) && (NULL != p_obj)); + PAR_ASSERT(NULL != p_cfg); + + par_nvm_layout_pack_payload_bytes(p_cfg->type, &p_obj->data, p_payload); + record_buf[0] = par_nvm_layout_calc_crc(0U, p_payload, payload_size, false); + + return (ePAR_OK == p_store->write(addr, record_size, record_buf)) ? ePAR_OK : ePAR_ERROR_NVM; +} + +/** + * @brief Validate one loaded canonical object against the current live schema. + * + * @param par_num Live parameter number expected at this slot. + * @param p_obj Canonical object loaded from NVM. + * @param pp_reason Output short mismatch reason for diagnostics. + * @param p_stored_id Output stored ID value when the layout carries one. + * @return Operation status. + */ +static par_status_t par_nvm_layout_grouped_payload_only_validate_loaded_obj(const par_num_t par_num, + const par_nvm_data_obj_t * const p_obj, + const char ** const pp_reason, + uint16_t * const p_stored_id) +{ + PAR_ASSERT((NULL != p_obj) && (NULL != pp_reason) && (NULL != p_stored_id)); + + (void)p_obj; + *pp_reason = NULL; + *p_stored_id = par_cfg_get_param_id_const(par_num); + return ePAR_OK; +} + +/** + * @brief Return the stored-ID diagnostic value for an error path. + * + * @param par_num Live parameter number associated with the slot. + * @param p_obj Canonical object loaded from NVM. + * @return Stored ID or a layout-defined fallback value. + */ +static uint16_t par_nvm_layout_grouped_payload_only_get_error_stored_id(const par_num_t par_num, + const par_nvm_data_obj_t * const p_obj) +{ + (void)p_obj; + return par_cfg_get_param_id_const(par_num); +} + +/** + * @brief Decide whether the stored header remains compatible with this layout. + * + * @param p_head_obj Validated NVM header object. + * @return Layout-specific compatibility decision. + */ +static par_nvm_compat_result_t par_nvm_layout_grouped_payload_only_check_compat(const par_nvm_head_obj_t * const p_head_obj) +{ + PAR_ASSERT(NULL != p_head_obj); + + if (p_head_obj->obj_nb > (uint16_t)PAR_PERSISTENT_COMPILE_COUNT) + { + return ePAR_NVM_COMPAT_REBUILD; + } + + if (p_head_obj->table_id != par_nvm_table_id_calc_for_count(p_head_obj->obj_nb)) + { + return ePAR_NVM_COMPAT_REBUILD; + } + + return (p_head_obj->obj_nb == (uint16_t)PAR_PERSISTENT_COMPILE_COUNT) ? ePAR_NVM_COMPAT_EXACT_MATCH : ePAR_NVM_COMPAT_REBUILD; +} + +#if (1 == PAR_CFG_NVM_WRITE_VERIFY_EN) +/** + * @brief Compare an expected object against a post-write read-back object. + * + * @param par_num Live parameter number associated with the slot. + * @param p_expected Expected canonical NVM object. + * @param p_actual Canonical object reloaded from storage. + * @return True when both objects match under this layout policy. + */ +static bool par_nvm_layout_grouped_payload_only_data_obj_matches(const par_num_t par_num, + const par_nvm_data_obj_t * const p_expected, + const par_nvm_data_obj_t * const p_actual) +{ + const par_cfg_t * const p_cfg = par_get_config(par_num); + const uint8_t payload_size = par_nvm_layout_payload_size_from_par_num(par_num); + uint8_t expected_payload[PAR_NVM_RECORD_DATA_SLOT_SIZE] = { 0U }; + uint8_t actual_payload[PAR_NVM_RECORD_DATA_SLOT_SIZE] = { 0U }; + + PAR_ASSERT((NULL != p_cfg) && (NULL != p_expected) && (NULL != p_actual)); + + par_nvm_layout_pack_payload_bytes(p_cfg->type, &p_expected->data, expected_payload); + par_nvm_layout_pack_payload_bytes(p_cfg->type, &p_actual->data, actual_payload); + return (0 == memcmp(expected_payload, actual_payload, payload_size)); +} +#endif + +/** + * @brief Concrete layout adapter bound by the common NVM core. + */ +static const par_nvm_layout_api_t g_par_nvm_layout_api = { + .record_size_from_par_num = par_nvm_layout_grouped_payload_only_record_size_from_par_num, + .addr_from_persist_idx = par_nvm_layout_grouped_payload_only_addr_from_persist_idx, + .populate_data_obj = par_nvm_layout_grouped_payload_only_populate_data_obj, + .read = par_nvm_layout_grouped_payload_only_read, + .write = par_nvm_layout_grouped_payload_only_write, + .validate_loaded_obj = par_nvm_layout_grouped_payload_only_validate_loaded_obj, + .get_error_stored_id = par_nvm_layout_grouped_payload_only_get_error_stored_id, + .check_compat = par_nvm_layout_grouped_payload_only_check_compat, +#if (1 == PAR_CFG_NVM_WRITE_VERIFY_EN) + .data_obj_matches = par_nvm_layout_grouped_payload_only_data_obj_matches, +#endif +}; + +/** + * @brief Return the concrete layout adapter selected for this build. + * + * @return Non-null pointer to this layout adapter. + */ +const par_nvm_layout_api_t *par_nvm_layout_init(void) +{ + return &g_par_nvm_layout_api; +} + +#endif /* grouped-payload-only */ diff --git a/src/persist/par_nvm_table_id.c b/src/persist/par_nvm_table_id.c new file mode 100644 index 0000000..2365d80 --- /dev/null +++ b/src/persist/par_nvm_table_id.c @@ -0,0 +1,96 @@ +/** + * @file par_nvm_table_id.c + * @brief Implement the parameter-table ID hash adapter. + * @author wdfk-prog + * @version 1.1 + * @date 2026-04-11 + * + * @copyright Copyright (c) 2026 Ziga Miklosic. Distributed under the MIT license. + * + * @note : + * @par Change Log: + * Date Version Author Description + * 2026-03-30 1.0 wdfk-prog first version + * 2026-04-11 1.1 wdfk-prog add stored-prefix table-ID calculation + */ +#include "par.h" +#include "persist/fnv.h" +#include "persist/par_nvm_table_id.h" + +/** + * @brief Update FNV-1a context with one platform-native scalar image. + * + * @param p_hval Pointer to rolling FNV-1a state. + * @param p_serialized_size Pointer to serialized byte counter. + * @param p_value Pointer to source scalar. + * @param value_size Scalar width in bytes. Supported values: 1, 2, 4. + */ +static void par_nvm_table_id_hash_update(Fnv32_t * const p_hval, + uint32_t * const p_serialized_size, + const void * const p_value, + const uint32_t value_size) +{ + *p_hval = fnv_32a_buf((void *)p_value, (size_t)value_size, *p_hval); + *p_serialized_size += value_size; +} + +/** + * @brief Calculate the live compatibility digest for one stored persistent prefix. + * + * @details The digest covers only metadata that affects the managed NVM image + * compatibility of the stored persistent prefix: schema version, selected + * record layout, stored persistent-object count, persistent-parameter order, + * and parameter type. Self-describing layouts additionally hash the external + * parameter ID. Payload-only layouts intentionally exclude external parameter + * IDs and rely on PAR_CFG_TABLE_ID_SCHEMA_VER to invalidate semantic-only + * prefix remaps that keep the same byte layout. Under the single-target + * native-endian profile, each scalar is hashed exactly as it is represented in + * memory on the running platform. + * + * @param persistent_count Number of persistent slots covered by the digest. + * @return Live digest for that stored prefix. + */ +uint32_t par_nvm_table_id_calc_for_count(const uint16_t persistent_count) +{ + Fnv32_t hval = FNV1_32A_INIT; + uint32_t serialized_size = 0U; + uint16_t hashed_count = 0U; + const uint32_t schema_version = (uint32_t)PAR_CFG_TABLE_ID_SCHEMA_VER; + const uint32_t record_layout = (uint32_t)PAR_CFG_NVM_RECORD_LAYOUT; + + PAR_ASSERT(persistent_count <= (uint16_t)PAR_PERSISTENT_COMPILE_COUNT); + + par_nvm_table_id_hash_update(&hval, &serialized_size, &schema_version, (uint32_t)sizeof(schema_version)); + par_nvm_table_id_hash_update(&hval, &serialized_size, &record_layout, (uint32_t)sizeof(record_layout)); + par_nvm_table_id_hash_update(&hval, &serialized_size, &persistent_count, (uint32_t)sizeof(persistent_count)); + + for (par_num_t par_num = 0U; par_num < ePAR_NUM_OF; par_num++) + { + const par_cfg_t * const p_cfg = par_get_config(par_num); + const uint8_t type = (uint8_t)p_cfg->type; + + if (false == p_cfg->persistent) + { + continue; + } + + if (hashed_count >= persistent_count) + { + break; + } + + par_nvm_table_id_hash_update(&hval, &serialized_size, &type, (uint32_t)sizeof(type)); + +#if (1 == PAR_CFG_NVM_RECORD_LAYOUT_HAS_STORED_ID) + { + const uint16_t parameter_id = par_cfg_get_param_id_const(par_num); + par_nvm_table_id_hash_update(&hval, &serialized_size, ¶meter_id, (uint32_t)sizeof(parameter_id)); + } +#endif + hashed_count++; + } + + PAR_ASSERT(hashed_count == persistent_count); + PAR_ASSERT(serialized_size > 0U); + return (uint32_t)hval; +} diff --git a/src/persist/par_nvm_table_id.h b/src/persist/par_nvm_table_id.h new file mode 100644 index 0000000..0109372 --- /dev/null +++ b/src/persist/par_nvm_table_id.h @@ -0,0 +1,33 @@ +/** + * @file par_nvm_table_id.h + * @brief Declare the parameter-table ID hash adapter. + * @author wdfk-prog + * @version 1.0 + * @date 2026-03-30 + * + * @copyright Copyright (c) 2026 Ziga Miklosic. Distributed under the MIT license. + * + * @note : + * @par Change Log: + * Date Version Author Description + * 2026-03-30 1.0 wdfk-prog first version + */ +#ifndef _PAR_NVM_TABLE_ID_H_ +#define _PAR_NVM_TABLE_ID_H_ + +#include +/** + * @brief Calculate the live compatibility digest for one stored persistent prefix. + * + * @details The caller provides the number of stored persistent slots that are + * part of the managed NVM image. The digest always covers that stored prefix. + * Layouts with stored IDs additionally hash external parameter IDs, while + * payload-only layouts intentionally exclude external parameter IDs and track + * only byte-layout compatibility for that prefix. + * + * @param persistent_count Number of persistent slots covered by the digest. + * @return Platform-native 32-bit FNV-1a digest. + */ +uint32_t par_nvm_table_id_calc_for_count(uint16_t persistent_count); + +#endif /* _PAR_NVM_TABLE_ID_H_ */ diff --git a/src/port/par_atomic.h b/src/port/par_atomic.h new file mode 100644 index 0000000..c37a3c9 --- /dev/null +++ b/src/port/par_atomic.h @@ -0,0 +1,235 @@ +/** + * @file par_atomic.h + * @brief Define atomic helper types and macros for parameter storage. + * @author wdfk-prog + * @version 1.0 + * @date 2026-03-27 + * + * @copyright Copyright (c) 2026 Ziga Miklosic. Distributed under the MIT license. + * + * @note : + * @par Change Log: + * Date Version Author Description + * 2026-03-27 1.0 wdfk-prog first version + */ +#ifndef PAR_ATOMIC_H +#define PAR_ATOMIC_H + +#include +/** + * @brief Compile-time definitions. + */ +/** + * @brief C11 atomic backend selector. + */ +#define PAR_ATOMIC_BACKEND_C11 1 + +/** + * @brief Port-specific atomic backend selector. + */ +#define PAR_ATOMIC_BACKEND_PORT 2 + +#ifndef PAR_ATOMIC_BACKEND +/** + * @brief Default atomic backend selection. + */ +#define PAR_ATOMIC_BACKEND PAR_ATOMIC_BACKEND_C11 +#endif + +/** + * @brief Atomic shared-storage contract for backend implementers. + * + * @note Backends used with static shared storage mode must guarantee identical. + * object representation for the following type groups: + * - par_atomic_u8_t and par_atomic_i8_t. + * - par_atomic_u16_t and par_atomic_i16_t. + * - par_atomic_u32_t, par_atomic_i32_t and par_atomic_f32_t. + * + * @note If a port backend cannot satisfy this contract, static shared storage. + * mode is not supported for that backend. + */ + +/** + * @brief List of integral types supported by atomic load/store helpers. + * + * @param X Macro invoked as X(tag, type). + */ +#define PAR_ATOMIC_INTEGRAL_TYPE_LIST(X) \ + X(u8, uint8_t) \ + X(i8, int8_t) \ + X(u16, uint16_t) \ + X(i16, int16_t) \ + X(u32, uint32_t) \ + X(i32, int32_t) + +/** + * @brief List of all scalar types supported by atomic helpers. + * + * @param X Macro invoked as X(tag, type). + */ +#define PAR_ATOMIC_TYPE_LIST(X) \ + PAR_ATOMIC_INTEGRAL_TYPE_LIST(X) \ + X(f32, float) + +/** + * @brief List of types supported by atomic fetch-and and fetch-or helpers. + * + * @param X Macro invoked as X(tag, type). + */ +#define PAR_ATOMIC_FETCH_TYPE_LIST(X) \ + X(u8, uint8_t) \ + X(u16, uint16_t) \ + X(u32, uint32_t) + +#if (PAR_ATOMIC_BACKEND == PAR_ATOMIC_BACKEND_C11) + +#include + +/** + * @brief Declare atomic typedef for selected scalar type. + * + * @param tag Type tag suffix. + * @param type Scalar type wrapped by _Atomic. + */ +#define PAR_ATOMIC_DECLARE_TYPE(tag, type) \ + typedef _Atomic type par_atomic_##tag##_t; + +PAR_ATOMIC_TYPE_LIST(PAR_ATOMIC_DECLARE_TYPE) + +#undef PAR_ATOMIC_DECLARE_TYPE + +/** + * @brief Define atomic load and store helper functions. + * + * @param tag Type tag suffix. + * @param type Scalar type of generated helpers. + */ +#define PAR_ATOMIC_DEFINE_LOAD_STORE(tag, type) \ + static inline type par_atomic_load_##tag(const par_atomic_##tag##_t *ptr) \ + { \ + return atomic_load_explicit(ptr, memory_order_relaxed); \ + } \ + \ + static inline void par_atomic_store_##tag(par_atomic_##tag##_t *ptr, \ + type value) \ + { \ + atomic_store_explicit(ptr, value, memory_order_relaxed); \ + } + +PAR_ATOMIC_INTEGRAL_TYPE_LIST(PAR_ATOMIC_DEFINE_LOAD_STORE) + +#undef PAR_ATOMIC_DEFINE_LOAD_STORE +/** + * @brief Load floating-point atomic value. + * + * @note "atomic_load_explicit" does not support float data type in this. + * implementation, therefore GCC/Clang built-in primitive. + * "__atomic_load" is used instead. + * + * @param ptr Pointer to atomic floating-point object. + * @return Current floating-point value. + */ +static inline float par_atomic_load_f32(const par_atomic_f32_t *ptr) +{ + float value; + + __atomic_load(ptr, &value, __ATOMIC_RELAXED); + + return value; +} +/** + * @brief Store floating-point atomic value. + * + * @note "atomic_store_explicit" does not support float data type in this. + * implementation, therefore GCC/Clang built-in primitive. + * "__atomic_store" is used instead. + * + * @param ptr Pointer to atomic floating-point object. + * @param value Value to store. + */ +static inline void par_atomic_store_f32(par_atomic_f32_t *ptr, float value) +{ + __atomic_store(ptr, &value, __ATOMIC_RELAXED); +} + +/** + * @brief Define atomic fetch-and helper function. + * + * @param tag Type tag suffix. + * @param type Scalar type of generated helper. + */ +#define PAR_ATOMIC_DEFINE_FETCH_AND(tag, type) \ + static inline type par_atomic_fetch_and_##tag(par_atomic_##tag##_t *ptr, \ + type value) \ + { \ + return atomic_fetch_and_explicit(ptr, value, memory_order_relaxed); \ + } + +PAR_ATOMIC_FETCH_TYPE_LIST(PAR_ATOMIC_DEFINE_FETCH_AND) + +#undef PAR_ATOMIC_DEFINE_FETCH_AND + +/** + * @brief Define atomic fetch-or helper function. + * + * @param tag Type tag suffix. + * @param type Scalar type of generated helper. + */ +#define PAR_ATOMIC_DEFINE_FETCH_OR(tag, type) \ + static inline type par_atomic_fetch_or_##tag(par_atomic_##tag##_t *ptr, \ + type value) \ + { \ + return atomic_fetch_or_explicit(ptr, value, memory_order_relaxed); \ + } + +PAR_ATOMIC_FETCH_TYPE_LIST(PAR_ATOMIC_DEFINE_FETCH_OR) + +#undef PAR_ATOMIC_DEFINE_FETCH_OR + +#elif (PAR_ATOMIC_BACKEND == PAR_ATOMIC_BACKEND_PORT) + +#include "../../par_atomic_port.h" + +#else + +#error "Unsupported PAR_ATOMIC_BACKEND" + +#endif +/** + * @brief Load atomic value by type tag. + * + * @param tag Type tag suffix. + * @param ptr Pointer to atomic object. + * @return Current atomic value. + */ +#define PAR_ATOMIC_LOAD(tag, ptr) par_atomic_load_##tag((ptr)) +/** + * @brief Store atomic value by type tag. + * + * @param tag Type tag suffix. + * @param ptr Pointer to atomic object. + * @param value Value to store. + */ +#define PAR_ATOMIC_STORE(tag, ptr, value) par_atomic_store_##tag((ptr), (value)) +/** + * @brief Perform atomic fetch-and by type tag. + * + * @param tag Type tag suffix. + * @param ptr Pointer to atomic object. + * @param value Operand for bitwise AND. + * @return Previous atomic value. + */ +#define PAR_ATOMIC_FETCH_AND(tag, ptr, value) \ + par_atomic_fetch_and_##tag((ptr), (value)) +/** + * @brief Perform atomic fetch-or by type tag. + * + * @param tag Type tag suffix. + * @param ptr Pointer to atomic object. + * @param value Operand for bitwise OR. + * @return Previous atomic value. + */ +#define PAR_ATOMIC_FETCH_OR(tag, ptr, value) \ + par_atomic_fetch_or_##tag((ptr), (value)) + +#endif diff --git a/src/port/par_if.c b/src/port/par_if.c new file mode 100644 index 0000000..32bb640 --- /dev/null +++ b/src/port/par_if.c @@ -0,0 +1,294 @@ +/** + * @file par_if.c + * @brief Implement the parameter interface layer. + * @author Ziga Miklosic + * @version V3.0.1 + * @date 2026-01-29 + * + * @copyright Copyright (c) 2026 Ziga Miklosic. Distributed under the MIT license. + * + * @note : + * @par Change Log: + * Date Version Author Description + * 2026-01-29 V3.0.1 Ziga Miklosic first version + */ +/** + * @addtogroup PAR_IF + * @{ + * + * @brief Interface layer for device parameters. + * + * Put code that is platform depended inside code block start with. + * "USER_CODE_BEGIN" and with end of "USER_CODE_END". + */ +/** + * @brief Include dependencies. + */ +#include "port/par_if.h" + +#if (1 == PAR_CFG_IF_PORT_EN) +/** + * @brief Initialize low level interface. + * + * @note Default weak implementation keeps the interface optional. + * Integrator may provide a strong definition in port/par_if_port.c. + * + * @return Status of initialization. + */ +PAR_PORT_WEAK par_status_t par_if_init(void) +{ + return ePAR_OK; +} +/** + * @brief De-initialize low level interface. + * + * @note Default weak implementation keeps the interface optional. + * Integrator may provide a strong definition in port/par_if_port.c. + * + * @return Status of de-initialization. + */ +PAR_PORT_WEAK par_status_t par_if_deinit(void) +{ + return ePAR_OK; +} +/** + * @brief Acquire mutex for specified parameter. + * + * @note Default weak implementation does not provide locking. + * Integrator may provide a strong definition in port/par_if_port.c. + * + * @param par_num Parameter number (enumeration). + * @return Status of operation. + */ +PAR_PORT_WEAK par_status_t par_if_aquire_mutex(const par_num_t par_num) +{ + (void)par_num; + return ePAR_OK; +} +/** + * @brief Release mutex for specified parameter. + * + * @note Default weak implementation does not provide locking. + * Integrator may provide a strong definition in port/par_if_port.c. + * + * @param par_num Parameter number (enumeration). + */ +PAR_PORT_WEAK void par_if_release_mutex(const par_num_t par_num) +{ + (void)par_num; +} +/** + * @brief Accumulate CRC-16/CCITT-FALSE over serialized header bytes. + * + * @details Default weak implementation used when the integrator does not + * provide a platform-specific override. + * + * @param crc Current CRC-16 accumulator value. + * @param p_data Pointer to serialized bytes. + * @param size Number of bytes to process. + * @return Updated CRC-16 value. + */ +PAR_PORT_WEAK uint16_t par_if_crc16_accumulate(uint16_t crc, const uint8_t * const p_data, const uint32_t size) +{ + const uint16_t poly = 0x1021U; + + if (0U == size) + { + return crc; + } + + PAR_ASSERT(NULL != p_data); + + for (uint32_t i = 0U; i < size; ++i) + { + crc ^= (uint16_t)((uint16_t)p_data[i] << 8U); + for (uint8_t bit = 0U; bit < 8U; ++bit) + { + if (0U != (crc & 0x8000U)) + { + crc = (uint16_t)((crc << 1U) ^ poly); + } + else + { + crc = (uint16_t)(crc << 1U); + } + } + } + + return crc; +} +/** + * @brief Accumulate CRC-8 over serialized record bytes. + * + * @details Default weak implementation used when the integrator does not + * provide a platform-specific override. + * + * @param crc Current CRC-8 accumulator value. + * @param p_data Pointer to serialized bytes. + * @param size Number of bytes to process. + * @return Updated CRC-8 value. + */ +PAR_PORT_WEAK uint8_t par_if_crc8_accumulate(uint8_t crc, const uint8_t * const p_data, const uint32_t size) +{ + const uint8_t poly = 0x07U; + + if (0U == size) + { + return crc; + } + + PAR_ASSERT(NULL != p_data); + + for (uint32_t i = 0U; i < size; ++i) + { + crc ^= p_data[i]; + for (uint8_t bit = 0U; bit < 8U; ++bit) + { + if (0U != (crc & 0x80U)) + { + crc = (uint8_t)((crc << 1U) ^ poly); + } + else + { + crc = (uint8_t)(crc << 1U); + } + } + } + + return crc; +} + +#else + +/** + * @brief USER INCLUDES BEGIN... + */ + +#include "common/utils/src/utils.h" +#include "cmsis_os2.h" + +/** + * @brief USER INCLUDES END... + */ +/** + * @brief Compile-time definitions. + */ +/** + * @brief USER DEFINITIONS BEGIN... + */ + +/** + * @brief Parameter mutex timeout. + * @details Unit: ms. + */ +#define PAR_CFG_MUTEX_TIMEOUT_MS (10) + +/** + * @brief USER DEFINITIONS END... + */ +/** + * @brief Module-scope variables. + */ +/** + * @brief USER VARIABLES BEGIN... + */ + +/** + * @brief Parameters OS mutex. + */ +static osMutexId_t g_par_mutex_id = NULL; +const osMutexAttr_t g_par_mutex_attr = { + .name = "par", + .attr_bits = (osMutexPrioInherit), +}; +/** + * @brief USER VARIABLES END... + */ +/** + * @brief Function declarations and definitions. + */ +/** + * @brief Initialize low level interface. + * + * @note User shall provide definition of that function based on used platform! + * + * @return Status of initialization. + */ +par_status_t par_if_init(void) +{ + par_status_t status = ePAR_OK; + g_par_mutex_id = osMutexNew(&g_par_mutex_attr); + + if (NULL == g_par_mutex_id) + { + status = ePAR_ERROR; + } + + return status; +} +/** + * @brief De-initialize low level interface. + * + * @note User shall provide definition of that function based on used platform! + * + * @return Status of de-initialization. + */ +par_status_t par_if_deinit(void) +{ + par_status_t status = ePAR_OK; + osMutexId_t mutex_id = g_par_mutex_id; + + if (NULL == mutex_id) + { + status = ePAR_OK; + } + else if (osOK != osMutexDelete(mutex_id)) + { + status = ePAR_ERROR; + } + g_par_mutex_id = NULL; + + return status; +} +/** + * @brief Acquire mutex for specified parameter. + * + * @note User shall provide definition of that function based on used platform! + * + * If mutex is not needed leave empty space between user code begin and end. + * + * @param par_num Parameter number (enumeration). + * @return Status of operation. + */ +par_status_t par_if_aquire_mutex(const par_num_t par_num) +{ + par_status_t status = ePAR_OK; + + UNUSED(par_num); + if (osOK != osMutexAcquire(g_par_mutex_id, PAR_CFG_MUTEX_TIMEOUT_MS)) + { + status = ePAR_ERROR; + } + + return status; +} +/** + * @brief Release mutex for specified parameter. + * + * @note User shall provide definition of that function based on used platform! + * + * If mutex is not needed leave empty space between user code begin and end. + * + * @param par_num Parameter number (enumeration). + * @return Status of operation. + */ +void par_if_release_mutex(const par_num_t par_num) +{ + UNUSED(par_num); + osMutexRelease(g_par_mutex_id); +} + +#endif +/** + * @} + */ diff --git a/src/port/par_if.h b/src/port/par_if.h new file mode 100644 index 0000000..49c7427 --- /dev/null +++ b/src/port/par_if.h @@ -0,0 +1,90 @@ +/** + * @file par_if.h + * @brief Declare the parameter interface layer. + * @author Ziga Miklosic + * @version V3.0.1 + * @date 2026-01-29 + * + * @copyright Copyright (c) 2026 Ziga Miklosic. Distributed under the MIT license. + * + * @note : + * @par Change Log: + * Date Version Author Description + * 2026-01-29 V3.0.1 Ziga Miklosic first version + */ +/** + * @addtogroup PAR_IF + * @{ + */ + +#ifndef _PAR_IF_H_ +#define _PAR_IF_H_ +/** + * @brief Include dependencies. + */ +#include +#include "par.h" +/** + * @brief Compile-time definitions. + */ +/** + * @brief Initial value for CRC-16/CCITT-FALSE accumulation. + */ +#define PAR_IF_CRC16_INIT ((uint16_t)0xFFFFU) + +/** + * @brief Initial value for CRC-8 accumulation. + */ +#define PAR_IF_CRC8_INIT ((uint8_t)0x00U) +/** + * @brief Function declarations. + */ +/** + * @brief Initialize the interface layer. + * @return Operation status. + */ +par_status_t par_if_init(void); +/** + * @brief Deinitialize the interface layer. + * @return Operation status. + */ +par_status_t par_if_deinit(void); +/** + * @brief Acquire the parameter mutex for one parameter path. + * @param par_num Parameter number. + * @return Operation status. + */ +par_status_t par_if_aquire_mutex(const par_num_t par_num); +/** + * @brief Release the parameter mutex for one parameter path. + * @param par_num Parameter number. + */ +void par_if_release_mutex(const par_num_t par_num); +/** + * @brief Accumulate CRC-16 for serialized NVM header content. + * + * @details The portable core provides a weak software default in + * src/port/par_if.c. The integrator may replace it with a strong platform + * implementation, for example one backed by a hardware CRC engine. + * + * @param crc Current CRC-16 accumulator value. + * @param p_data Pointer to serialized bytes. + * @param size Number of bytes to process. + * @return Updated CRC-16 value. + */ +uint16_t par_if_crc16_accumulate(uint16_t crc, const uint8_t * const p_data, const uint32_t size); +/** + * @brief Accumulate CRC-8 for one serialized NVM data record. + * + * @details The portable core provides a weak software default in + * src/port/par_if.c. The integrator may replace it with a strong platform + * implementation, for example one backed by a hardware CRC engine. + * + * @param crc Current CRC-8 accumulator value. + * @param p_data Pointer to serialized bytes. + * @param size Number of bytes to process. + * @return Updated CRC-8 value. + */ +uint8_t par_if_crc8_accumulate(uint8_t crc, const uint8_t * const p_data, const uint32_t size); + +#endif /* _PAR_IF_H_ */ diff --git a/template/par_cfg.ctmp b/template/par_cfg.ctmp deleted file mode 100644 index 06a4e04..0000000 --- a/template/par_cfg.ctmp +++ /dev/null @@ -1,271 +0,0 @@ -// Copyright (c) 2026 Ziga Miklosic -// All Rights Reserved -// This software is under MIT licence (https://opensource.org/licenses/MIT) -//////////////////////////////////////////////////////////////////////////////// -/** -*@file par_cfg.c -*@brief Configuration for device parameters -*@author Ziga Miklosic -*@email ziga.miklosic@gmail.com -*@date 29.01.2026 -*@version V3.0.1 -*/ -//////////////////////////////////////////////////////////////////////////////// -/** -*@addtogroup PAR_CFG -* @{ -* -* Configuration for device parameters -* -* User shall put code inside inside code block start with -* "USER_CODE_BEGIN" and with end of "USER_CODE_END". -*/ -//////////////////////////////////////////////////////////////////////////////// - -//////////////////////////////////////////////////////////////////////////////// -// Includes -//////////////////////////////////////////////////////////////////////////////// -#include "par_cfg.h" -#include "parameters/src/par.h" - -//////////////////////////////////////////////////////////////////////////////// -// Definitions -//////////////////////////////////////////////////////////////////////////////// - -//////////////////////////////////////////////////////////////////////////////// -// Variables -//////////////////////////////////////////////////////////////////////////////// - -/** - * Parameters definitions - * - * @brief - * - * Each defined parameter has following properties: - * - * i) Parameter ID: Unique parameter identification number. ID shall not be duplicated. - * ii) Name: Parameter name. Max. length of 32 chars. - * iii) Min: Parameter minimum value. Min value must be less than max value. - * iv) Max: Parameter maximum value. Max value must be more than min value. - * v) Def: Parameter default value. Default value must lie between interval: [min, max] - * vi) Unit: In case parameter shows physical value. Max. length of 32 chars. - * vii) Data type: Parameter data type. Supported types: uint8_t, int8_t, uint16_t, int16_t, uint32_t, int32_t and float32_t - * viii) Access: Access type visible from external device such as PC. Either ReadWrite or ReadOnly. - * ix) Persistence: Tells if parameter value is being written into NVM. - * - * - * @note User shall fill up wanted parameter definitions! - */ -static const par_cfg_t g_par_table[ePAR_NUM_OF] = -{ - - // USER CODE BEGIN... - - // ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ - // ID Name Min Max Def Unit Data type PC Access Persistent Description - // ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ - - - // CHANNEL 1 - // ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ - - // Channel 1 control - [ePAR_CH1_CTRL] = { .id = 0, .name = "Ch1 Control", .min.u8 = 0U, .max.u8 = 2U, .def.u8 = 2U, .unit = NULL, .type = ePAR_TYPE_U8, .access = ePAR_ACCESS_RW, .persistant = false, .desc = "Channel 1 control: 0-Normal | 1-Short | 2-Open. NOTE: If status is then it will force to Open!" }, - [ePAR_CH1_AFE_MEAS_EN] = { .id = 1, .name = "Ch1 AFE Measurement Control", .min.u8 = 0U, .max.u8 = 1U, .def.u8 = 1U, .unit = NULL, .type = ePAR_TYPE_U8, .access = ePAR_ACCESS_RW, .persistant = true, .desc = "Channel 1 control AFE measurement usage for calculations: 0-Disable | 1-Enable" }, - [ePAR_CH1_TEST_MODE_EN] = { .id = 2, .name = "Ch1 Test Mode", .min.u8 = 0U, .max.u8 = 1U, .def.u8 = 0U, .unit = NULL, .type = ePAR_TYPE_U8, .access = ePAR_ACCESS_RW, .persistant = false, .desc = "Channel 1 test mode enable: 0-Disable | 1-Enable. NOTE: This will put channel to Normal state - ignoring detection logic!" }, - [ePAR_CH1_REF_SEL] = { .id = 3, .name = "Ch1 Reference Selection", .min.u8 = 0U, .max.u8 = 4U, .def.u8 = 0U, .unit = NULL, .type = ePAR_TYPE_U8, .access = ePAR_ACCESS_RW, .persistant = true, .desc = "Channel 1 reference selection: 0-Temperature | 1-Resistance | 2-Vout | 3-Isink | 4-Vset" }, - [ePAR_CH1_REF_VAL] = { .id = 4, .name = "Ch1 Reference Value", .min.f32 = -1E6f, .max.f32 = 1E6f, .def.f32 = 0.0f, .unit = NULL, .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RW, .persistant = true, .desc = "Channel 1 reference value based on control variable set" }, - - // TODO: add function generator! - - // Channel 1 status - [ePAR_CH1_STATUS] = { .id = 10, .name = "Ch1 Status", .min.u8 = 0U, .max.u8 = 3U, .def.u8 = 3U, .unit = NULL, .type = ePAR_TYPE_U8, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 1 status. 0-Normal | 1-Short | 2-Open | 3-Not Connected" }, - [ePAR_CH1_FSM_STATE] = { .id = 11, .name = "Ch1 FSM State", .min.u8 = 0U, .max.u8 = 3U, .def.u8 = 0U, .unit = NULL, .type = ePAR_TYPE_U8, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 1 FSM state. 0-Idle | 1-Measure Vcc | 2-Measure Res | 3-Normal" }, - - // Channel 1 reference values - [ePAR_CH1_TSIM] = { .id = 20, .name = "Ch1 Ref Temperature", .min.f32 = -20.0f, .max.f32 = 350.0f, .def.f32 = 0.0f, .unit = "degC", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 1 temperature in degree C" }, - [ePAR_CH1_RSIM] = { .id = 21, .name = "Ch1 Ref Resistance", .min.f32 = 920.0f, .max.f32 = 2300.0f, .def.f32 = 920.0f, .unit = "ohm", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 1 resistance reference in ohm" }, - [ePAR_CH1_VOUT] = { .id = 22, .name = "Ch1 Ref Vout", .min.f32 = 0.85f, .max.f32 = 1.58f, .def.f32 = 0.85f, .unit = "V", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 1 output voltage in V" }, - [ePAR_CH1_ISINK] = { .id = 23, .name = "Ch1 Ref Isink", .min.f32 = 658.5f, .max.f32 = 950.0f, .def.f32 = 658.5f, .unit = "uA", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 1 sink current reference in uA" }, - [ePAR_CH1_VSET] = { .id = 24, .name = "Ch1 Ref Vset", .min.f32 = 1.7955f, .max.f32 = 2.548f, .def.f32 = 1.7955f, .unit = "V", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 1 Vset (reference) voltage in V" }, - [ePAR_CH1_VTH] = { .id = 25, .name = "Ch1 Ref Vth", .min.f32 = 0.0f, .max.f32 = 3.3f, .def.f32 = 0.0f, .unit = "V", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 1 Vth (thevenin) voltage in V. Equivalent circuit from both DAC combined." }, - [ePAR_CH1_VDAC_C] = { .id = 26, .name = "Ch1 Ref Vdac Coarse", .min.f32 = 0.0f, .max.f32 = 3.3f, .def.f32 = 0.0f, .unit = "V", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 1 Vdac coarse voltage in V" }, - [ePAR_CH1_VDAC_F] = { .id = 27, .name = "Ch1 Ref Vdac Fine", .min.f32 = 0.0f, .max.f32 = 3.3f, .def.f32 = 0.0f, .unit = "V", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 1 Vdac fine voltage in V" }, - [ePAR_CH1_VDAC_F_RAW] = { .id = 28, .name = "Ch1 Ref Raw DAC Fine", .min.u16 = 0U, .max.u16 = 4095U, .def.u16 = 0U, .unit = NULL, .type = ePAR_TYPE_U16, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 1 raw fine DAC value" }, - [ePAR_CH1_VDAC_C_RAW] = { .id = 29, .name = "Ch1 Ref Raw DAC Coarse", .min.u16 = 0U, .max.u16 = 4095U, .def.u16 = 0U, .unit = NULL, .type = ePAR_TYPE_U16, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 1 raw coarse DAC value" }, - - // Channel 1 measurements - [ePAR_CH1_CUR] = { .id = 50, .name = "Ch1 Act Isink", .min.f32 = 0.0f, .max.f32 = 5000.0f, .def.f32 = 0.0f, .unit = "uA", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 1 actual current sink value in uA" }, - [ePAR_CH1_VOL] = { .id = 51, .name = "Ch1 Act Vout", .min.f32 = 0.0f, .max.f32 = 10.0f, .def.f32 = 0.0f, .unit = "V", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 1 actual output voltage value in V" }, - [ePAR_CH1_CUR_RAW] = { .id = 52, .name = "Ch1 Raw Isink", .min.u16 = 0U, .max.u16 = 8191U, .def.u16 = 0U, .unit = NULL, .type = ePAR_TYPE_U16, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 1 raw ADC current value" }, - [ePAR_CH1_VOL_RAW] = { .id = 53, .name = "Ch1 Raw Vout", .min.u16 = 0U, .max.u16 = 8191U, .def.u16 = 0U, .unit = NULL, .type = ePAR_TYPE_U16, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 1 raw ADC voltage value" }, - [ePAR_CH1_AFE_VCC] = { .id = 54, .name = "Ch1 AFE Vcc", .min.f32 = 0.0f, .max.f32 = 10.0f, .def.f32 = 0.0f, .unit = "V", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 1 measured AFE Vcc voltage value in V" }, - [ePAR_CH1_AFE_RES] = { .id = 55, .name = "Ch1 AFE Res", .min.f32 = 0.0f, .max.f32 = 5.0e3f, .def.f32 = 0.0f, .unit = "ohm", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 1 measured AFE resistance (pull-up + series) value in ohm" }, - - - // CHANNEL 2 - // ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ - - // Channel 2 control - [ePAR_CH2_CTRL] = { .id = 100, .name = "Ch2 Control", .min.u8 = 0U, .max.u8 = 2U, .def.u8 = 2U, .unit = NULL, .type = ePAR_TYPE_U8, .access = ePAR_ACCESS_RW, .persistant = false, .desc = "Channel 2 control: 0-Normal | 1-Short | 2-Open. NOTE: If status is then it will force to Open!" }, - [ePAR_CH2_AFE_MEAS_EN] = { .id = 101, .name = "Ch2 AFE Measurement Control", .min.u8 = 0U, .max.u8 = 1U, .def.u8 = 1U, .unit = NULL, .type = ePAR_TYPE_U8, .access = ePAR_ACCESS_RW, .persistant = true, .desc = "Channel 2 control AFE measurement usage for calculations: 0-Disable | 1-Enable" }, - [ePAR_CH2_TEST_MODE_EN] = { .id = 102, .name = "Ch2 Test Mode", .min.u8 = 0U, .max.u8 = 1U, .def.u8 = 0U, .unit = NULL, .type = ePAR_TYPE_U8, .access = ePAR_ACCESS_RW, .persistant = false, .desc = "Channel 2 test mode enable: 0-Disable | 1-Enable. NOTE: This will put channel to Normal state - ignoring detection logic!" }, - [ePAR_CH2_REF_SEL] = { .id = 103, .name = "Ch2 Reference Selection", .min.u8 = 0U, .max.u8 = 4U, .def.u8 = 0U, .unit = NULL, .type = ePAR_TYPE_U8, .access = ePAR_ACCESS_RW, .persistant = true, .desc = "Channel 2 reference selection: 0-Temperature | 1-Resistance | 2-Vout | 3-Isink | 4-Vset" }, - [ePAR_CH2_REF_VAL] = { .id = 104, .name = "Ch2 Reference Value", .min.f32 = -1E6f, .max.f32 = 1E6f, .def.f32 = 0.0f, .unit = NULL, .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RW, .persistant = true, .desc = "Channel 2 reference value based on control variable set" }, - - // Channel 2 status - [ePAR_CH2_STATUS] = { .id = 110, .name = "Ch2 Status", .min.u8 = 0U, .max.u8 = 3U, .def.u8 = 3U, .unit = NULL, .type = ePAR_TYPE_U8, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 2 status. 0-Normal | 1-Short | 2-Open | 3-Not Connected" }, - [ePAR_CH2_FSM_STATE] = { .id = 111, .name = "Ch2 FSM State", .min.u8 = 0U, .max.u8 = 3U, .def.u8 = 0U, .unit = NULL, .type = ePAR_TYPE_U8, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 2 FSM state. 0-Idle | 1-Measure Vcc | 2-Measure Res | 3-Normal" }, - - // Channel 2 reference values - [ePAR_CH2_TSIM] = { .id = 120, .name = "Ch2 Ref Temperature", .min.f32 = -20.0f, .max.f32 = 350.0f, .def.f32 = 0.0f, .unit = "degC", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 2 temperature in degree C" }, - [ePAR_CH2_RSIM] = { .id = 121, .name = "Ch2 Ref Resistance", .min.f32 = 920.0f, .max.f32 = 2300.0f, .def.f32 = 920.0f, .unit = "ohm", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 2 resistance reference in ohm" }, - [ePAR_CH2_VOUT] = { .id = 122, .name = "Ch2 Ref Vout", .min.f32 = 0.85f, .max.f32 = 1.58f, .def.f32 = 0.85f, .unit = "V", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 2 output voltage in V" }, - [ePAR_CH2_ISINK] = { .id = 123, .name = "Ch2 Ref Isink", .min.f32 = 658.5f, .max.f32 = 950.0f, .def.f32 = 658.5f, .unit = "uA", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 2 sink current reference in uA" }, - [ePAR_CH2_VSET] = { .id = 124, .name = "Ch2 Ref Vset", .min.f32 = 1.7955f, .max.f32 = 2.548f, .def.f32 = 1.7955f, .unit = "V", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 2 Vset (reference) voltage in V" }, - [ePAR_CH2_VTH] = { .id = 125, .name = "Ch2 Ref Vth", .min.f32 = 0.0f, .max.f32 = 3.3f, .def.f32 = 0.0f, .unit = "V", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 2 Vth (thevenin) voltage in V. Equivalent circuit from both DAC combined." }, - [ePAR_CH2_VDAC_C] = { .id = 126, .name = "Ch2 Ref Vdac Coarse", .min.f32 = 0.0f, .max.f32 = 3.3f, .def.f32 = 0.0f, .unit = "V", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 2 Vdac coarse voltage in V" }, - [ePAR_CH2_VDAC_F] = { .id = 127, .name = "Ch2 Ref Vdac Fine", .min.f32 = 0.0f, .max.f32 = 3.3f, .def.f32 = 0.0f, .unit = "V", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 2 Vdac fine voltage in V" }, - [ePAR_CH2_VDAC_F_RAW] = { .id = 128, .name = "Ch2 Ref Raw DAC Fine", .min.u16 = 0U, .max.u16 = 4095U, .def.u16 = 0U, .unit = NULL, .type = ePAR_TYPE_U16, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 2 raw fine DAC value" }, - [ePAR_CH2_VDAC_C_RAW] = { .id = 129, .name = "Ch2 Ref Raw DAC Coarse", .min.u16 = 0U, .max.u16 = 4095U, .def.u16 = 0U, .unit = NULL, .type = ePAR_TYPE_U16, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 2 raw coarse DAC value" }, - - // Channel 2 measurements - [ePAR_CH2_CUR] = { .id = 150, .name = "Ch2 Act Isink", .min.f32 = 0.0f, .max.f32 = 5000.0f, .def.f32 = 0.0f, .unit = "uA", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 2 actual current sink value in uA" }, - [ePAR_CH2_VOL] = { .id = 151, .name = "Ch2 Act Vout", .min.f32 = 0.0f, .max.f32 = 10.0f, .def.f32 = 0.0f, .unit = "V", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 2 actual output voltage value in V" }, - [ePAR_CH2_CUR_RAW] = { .id = 152, .name = "Ch2 Raw Isink", .min.u16 = 0U, .max.u16 = 8191U, .def.u16 = 0U, .unit = NULL, .type = ePAR_TYPE_U16, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 2 raw ADC current value" }, - [ePAR_CH2_VOL_RAW] = { .id = 153, .name = "Ch2 Raw Vout", .min.u16 = 0U, .max.u16 = 8191U, .def.u16 = 0U, .unit = NULL, .type = ePAR_TYPE_U16, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 2 raw ADC voltage value" }, - [ePAR_CH2_AFE_VCC] = { .id = 154, .name = "Ch2 AFE Vcc", .min.f32 = 0.0f, .max.f32 = 10.0f, .def.f32 = 0.0f, .unit = "V", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 2 measured AFE Vcc voltage value in V" }, - [ePAR_CH2_AFE_RES] = { .id = 155, .name = "Ch2 AFE Res", .min.f32 = 0.0f, .max.f32 = 5.0e3f, .def.f32 = 0.0f, .unit = "ohm", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 2 measured AFE resistance (pull-up + series) value in ohm" }, - - - // CHANNEL 3 - // ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ - - // Channel 3 control - [ePAR_CH3_CTRL] = { .id = 200, .name = "Ch3 Control", .min.u8 = 0U, .max.u8 = 2U, .def.u8 = 2U, .unit = NULL, .type = ePAR_TYPE_U8, .access = ePAR_ACCESS_RW, .persistant = false, .desc = "Channel 3 control: 0-Normal | 1-Short | 2-Open. NOTE: If status is then it will force to Open!" }, - [ePAR_CH3_AFE_MEAS_EN] = { .id = 201, .name = "Ch3 AFE Measurement Control", .min.u8 = 0U, .max.u8 = 1U, .def.u8 = 1U, .unit = NULL, .type = ePAR_TYPE_U8, .access = ePAR_ACCESS_RW, .persistant = true, .desc = "Channel 3 control AFE measurement usage for calculations: 0-Disable | 1-Enable" }, - [ePAR_CH3_TEST_MODE_EN] = { .id = 202, .name = "Ch3 Test Mode", .min.u8 = 0U, .max.u8 = 1U, .def.u8 = 0U, .unit = NULL, .type = ePAR_TYPE_U8, .access = ePAR_ACCESS_RW, .persistant = false, .desc = "Channel 3 test mode enable: 0-Disable | 1-Enable. NOTE: This will put channel to Normal state - ignoring detection logic!" }, - [ePAR_CH3_REF_SEL] = { .id = 203, .name = "Ch3 Reference Selection", .min.u8 = 0U, .max.u8 = 4U, .def.u8 = 0U, .unit = NULL, .type = ePAR_TYPE_U8, .access = ePAR_ACCESS_RW, .persistant = true, .desc = "Channel 3 reference selection: 0-Temperature | 1-Resistance | 2-Vout | 3-Isink | 4-Vset" }, - [ePAR_CH3_REF_VAL] = { .id = 204, .name = "Ch3 Reference Value", .min.f32 = -1E6f, .max.f32 = 1E6f, .def.f32 = 0.0f, .unit = NULL, .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RW, .persistant = true, .desc = "Channel 3 reference value based on control variable set" }, - - // Channel 3 status - [ePAR_CH3_STATUS] = { .id = 210, .name = "Ch3 Status", .min.u8 = 0U, .max.u8 = 3U, .def.u8 = 3U, .unit = NULL, .type = ePAR_TYPE_U8, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 3 status. 0-Normal | 1-Short | 2-Open | 3-Not Connected" }, - [ePAR_CH3_FSM_STATE] = { .id = 211, .name = "Ch3 FSM State", .min.u8 = 0U, .max.u8 = 3U, .def.u8 = 0U, .unit = NULL, .type = ePAR_TYPE_U8, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 3 FSM state. 0-Idle | 1-Measure Vcc | 2-Measure Res | 3-Normal" }, - - // Channel 3 reference values - [ePAR_CH3_TSIM] = { .id = 220, .name = "Ch3 Ref Temperature", .min.f32 = -20.0f, .max.f32 = 350.0f, .def.f32 = 0.0f, .unit = "degC", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 3 temperature in degree C" }, - [ePAR_CH3_RSIM] = { .id = 221, .name = "Ch3 Ref Resistance", .min.f32 = 920.0f, .max.f32 = 2300.0f, .def.f32 = 920.0f, .unit = "ohm", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 3 resistance reference in ohm" }, - [ePAR_CH3_VOUT] = { .id = 222, .name = "Ch3 Ref Vout", .min.f32 = 0.85f, .max.f32 = 1.58f, .def.f32 = 0.85f, .unit = "V", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 3 output voltage in V" }, - [ePAR_CH3_ISINK] = { .id = 223, .name = "Ch3 Ref Isink", .min.f32 = 658.5f, .max.f32 = 950.0f, .def.f32 = 658.5f, .unit = "uA", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 3 sink current reference in uA" }, - [ePAR_CH3_VSET] = { .id = 224, .name = "Ch3 Ref Vset", .min.f32 = 1.7955f, .max.f32 = 2.548f, .def.f32 = 1.7955f, .unit = "V", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 3 Vset (reference) voltage in V" }, - [ePAR_CH3_VTH] = { .id = 225, .name = "Ch3 Ref Vth", .min.f32 = 0.0f, .max.f32 = 3.3f, .def.f32 = 0.0f, .unit = "V", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 3 Vth (thevenin) voltage in V. Equivalent circuit from both DAC combined." }, - [ePAR_CH3_VDAC_C] = { .id = 226, .name = "Ch3 Ref Vdac Coarse", .min.f32 = 0.0f, .max.f32 = 3.3f, .def.f32 = 0.0f, .unit = "V", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 3 Vdac coarse voltage in V" }, - [ePAR_CH3_VDAC_F] = { .id = 227, .name = "Ch3 Ref Vdac Fine", .min.f32 = 0.0f, .max.f32 = 3.3f, .def.f32 = 0.0f, .unit = "V", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 3 Vdac fine voltage in V" }, - [ePAR_CH3_VDAC_F_RAW] = { .id = 228, .name = "Ch3 Ref Raw DAC Fine", .min.u16 = 0U, .max.u16 = 4095U, .def.u16 = 0U, .unit = NULL, .type = ePAR_TYPE_U16, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 3 raw fine DAC value" }, - [ePAR_CH3_VDAC_C_RAW] = { .id = 229, .name = "Ch3 Ref Raw DAC Coarse", .min.u16 = 0U, .max.u16 = 4095U, .def.u16 = 0U, .unit = NULL, .type = ePAR_TYPE_U16, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 3 raw coarse DAC value" }, - - // Channel 3 measurements - [ePAR_CH3_CUR] = { .id = 250, .name = "Ch3 Act Isink", .min.f32 = 0.0f, .max.f32 = 5000.0f, .def.f32 = 0.0f, .unit = "uA", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 3 actual current sink value in uA" }, - [ePAR_CH3_VOL] = { .id = 251, .name = "Ch3 Act Vout", .min.f32 = 0.0f, .max.f32 = 10.0f, .def.f32 = 0.0f, .unit = "V", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 3 actual output voltage value in V" }, - [ePAR_CH3_CUR_RAW] = { .id = 252, .name = "Ch3 Raw Isink", .min.u16 = 0U, .max.u16 = 8191U, .def.u16 = 0U, .unit = NULL, .type = ePAR_TYPE_U16, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 3 raw ADC current value" }, - [ePAR_CH3_VOL_RAW] = { .id = 253, .name = "Ch3 Raw Vout", .min.u16 = 0U, .max.u16 = 8191U, .def.u16 = 0U, .unit = NULL, .type = ePAR_TYPE_U16, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 3 raw ADC voltage value" }, - [ePAR_CH3_AFE_VCC] = { .id = 254, .name = "Ch3 AFE Vcc", .min.f32 = 0.0f, .max.f32 = 10.0f, .def.f32 = 0.0f, .unit = "V", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 3 measured AFE Vcc voltage value in V" }, - [ePAR_CH3_AFE_RES] = { .id = 255, .name = "Ch3 AFE Res", .min.f32 = 0.0f, .max.f32 = 5.0e3f, .def.f32 = 0.0f, .unit = "ohm", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 3 measured AFE resistance (pull-up + series) value in ohm" }, - - - // CHANNEL 4 - // ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ - - // Channel 4 control - [ePAR_CH4_CTRL] = { .id = 300, .name = "Ch4 Control", .min.u8 = 0U, .max.u8 = 2U, .def.u8 = 2U, .unit = NULL, .type = ePAR_TYPE_U8, .access = ePAR_ACCESS_RW, .persistant = false, .desc = "Channel 4 control: 0-Normal | 1-Short | 2-Open. NOTE: If status is then it will force to Open!" }, - [ePAR_CH4_AFE_MEAS_EN] = { .id = 301, .name = "Ch4 AFE Measurement Control", .min.u8 = 0U, .max.u8 = 1U, .def.u8 = 1U, .unit = NULL, .type = ePAR_TYPE_U8, .access = ePAR_ACCESS_RW, .persistant = true, .desc = "Channel 4 control AFE measurement usage for calculations: 0-Disable | 1-Enable" }, - [ePAR_CH4_TEST_MODE_EN] = { .id = 302, .name = "Ch4 Test Mode", .min.u8 = 0U, .max.u8 = 1U, .def.u8 = 0U, .unit = NULL, .type = ePAR_TYPE_U8, .access = ePAR_ACCESS_RW, .persistant = false, .desc = "Channel 4 test mode enable: 0-Disable | 1-Enable. NOTE: This will put channel to Normal state - ignoring detection logic!" }, - [ePAR_CH4_REF_SEL] = { .id = 303, .name = "Ch4 Reference Selection", .min.u8 = 0U, .max.u8 = 4U, .def.u8 = 0U, .unit = NULL, .type = ePAR_TYPE_U8, .access = ePAR_ACCESS_RW, .persistant = true, .desc = "Channel 4 reference selection: 0-Temperature | 1-Resistance | 2-Vout | 3-Isink | 4-Vset" }, - [ePAR_CH4_REF_VAL] = { .id = 304, .name = "Ch4 Reference Value", .min.f32 = -1E6f, .max.f32 = 1E6f, .def.f32 = 0.0f, .unit = NULL, .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RW, .persistant = true, .desc = "Channel 4 reference value based on control variable set" }, - - // Channel 4 status - [ePAR_CH4_STATUS] = { .id = 310, .name = "Ch4 Status", .min.u8 = 0U, .max.u8 = 3U, .def.u8 = 3U, .unit = NULL, .type = ePAR_TYPE_U8, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 4 status. 0-Normal | 1-Short | 2-Open | 3-Not Connected" }, - [ePAR_CH4_FSM_STATE] = { .id = 311, .name = "Ch4 FSM State", .min.u8 = 0U, .max.u8 = 3U, .def.u8 = 0U, .unit = NULL, .type = ePAR_TYPE_U8, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 4 FSM state. 0-Idle | 1-Measure Vcc | 2-Measure Res | 3-Normal" }, - - // Channel 4 reference values - [ePAR_CH4_TSIM] = { .id = 320, .name = "Ch4 Ref Temperature", .min.f32 = -20.0f, .max.f32 = 350.0f, .def.f32 = 0.0f, .unit = "degC", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 4 temperature in degree C" }, - [ePAR_CH4_RSIM] = { .id = 321, .name = "Ch4 Ref Resistance", .min.f32 = 920.0f, .max.f32 = 2300.0f, .def.f32 = 920.0f, .unit = "ohm", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 4 resistance reference in ohm" }, - [ePAR_CH4_VOUT] = { .id = 322, .name = "Ch4 Ref Vout", .min.f32 = 0.85f, .max.f32 = 1.58f, .def.f32 = 0.85f, .unit = "V", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 4 output voltage in V" }, - [ePAR_CH4_ISINK] = { .id = 323, .name = "Ch4 Ref Isink", .min.f32 = 658.5f, .max.f32 = 950.0f, .def.f32 = 658.5f, .unit = "uA", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 4 sink current reference in uA" }, - [ePAR_CH4_VSET] = { .id = 324, .name = "Ch4 Ref Vset", .min.f32 = 1.7955f, .max.f32 = 2.548f, .def.f32 = 1.7955f, .unit = "V", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 4 Vset (reference) voltage in V" }, - [ePAR_CH4_VTH] = { .id = 325, .name = "Ch4 Ref Vth", .min.f32 = 0.0f, .max.f32 = 3.3f, .def.f32 = 0.0f, .unit = "V", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 4 Vth (thevenin) voltage in V. Equivalent circuit from both DAC combined." }, - [ePAR_CH4_VDAC_C] = { .id = 326, .name = "Ch4 Ref Vdac Coarse", .min.f32 = 0.0f, .max.f32 = 3.3f, .def.f32 = 0.0f, .unit = "V", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 4 Vdac coarse voltage in V" }, - [ePAR_CH4_VDAC_F] = { .id = 327, .name = "Ch4 Ref Vdac Fine", .min.f32 = 0.0f, .max.f32 = 3.3f, .def.f32 = 0.0f, .unit = "V", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 4 Vdac fine voltage in V" }, - [ePAR_CH4_VDAC_F_RAW] = { .id = 328, .name = "Ch4 Ref Raw DAC Fine", .min.u16 = 0U, .max.u16 = 4095U, .def.u16 = 0U, .unit = NULL, .type = ePAR_TYPE_U16, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 4 raw fine DAC value" }, - [ePAR_CH4_VDAC_C_RAW] = { .id = 329, .name = "Ch4 Ref Raw DAC Coarse", .min.u16 = 0U, .max.u16 = 4095U, .def.u16 = 0U, .unit = NULL, .type = ePAR_TYPE_U16, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 4 raw coarse DAC value" }, - - // Channel 4 measurements - [ePAR_CH4_CUR] = { .id = 350, .name = "Ch4 Act Isink", .min.f32 = 0.0f, .max.f32 = 5000.0f, .def.f32 = 0.0f, .unit = "uA", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 4 actual current sink value in uA" }, - [ePAR_CH4_VOL] = { .id = 351, .name = "Ch4 Act Vout", .min.f32 = 0.0f, .max.f32 = 10.0f, .def.f32 = 0.0f, .unit = "V", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 4 actual output voltage value in V" }, - [ePAR_CH4_CUR_RAW] = { .id = 352, .name = "Ch4 Raw Isink", .min.u16 = 0U, .max.u16 = 8191U, .def.u16 = 0U, .unit = NULL, .type = ePAR_TYPE_U16, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 4 raw ADC current value" }, - [ePAR_CH4_VOL_RAW] = { .id = 353, .name = "Ch4 Raw Vout", .min.u16 = 0U, .max.u16 = 8191U, .def.u16 = 0U, .unit = NULL, .type = ePAR_TYPE_U16, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 4 raw ADC voltage value" }, - [ePAR_CH4_AFE_VCC] = { .id = 354, .name = "Ch4 AFE Vcc", .min.f32 = 0.0f, .max.f32 = 10.0f, .def.f32 = 0.0f, .unit = "V", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 4 measured AFE Vcc voltage value in V" }, - [ePAR_CH4_AFE_RES] = { .id = 355, .name = "Ch4 AFE Res", .min.f32 = 0.0f, .max.f32 = 5.0e3f, .def.f32 = 0.0f, .unit = "ohm", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Channel 4 measured AFE resistance (pull-up + series) value in ohm" }, - - - // System status - [ePAR_SYS_STATUS] = { .id = 10000, .name = "System status", .min.u32 = 0, .max.u32 = UINT32_MAX, .def.u32 = 0, .unit = NULL, .type = ePAR_TYPE_U32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "0-None | For other look at sys_err.h" }, - [ePAR_SYS_CPU_LOAD] = { .id = 10010, .name = "CPU load", .min.f32 = 0, .max.f32 = 100.0f, .def.f32 = 0.0f, .unit = "%", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Current CPU load in %" }, - [ePAR_SYS_CPU_LOAD_MAX] = { .id = 10011, .name = "CPU Max. load", .min.f32 = 0, .max.f32 = 100.0f, .def.f32 = 0.0f, .unit = "%", .type = ePAR_TYPE_F32, .access = ePAR_ACCESS_RO, .persistant = false, .desc = "Maximum CPU load in %" }, - - // ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ - - - // USER CODE END... -}; - -/** - * Table size in bytes - */ -static const uint32_t gu32_par_table_size = sizeof( g_par_table ); - -//////////////////////////////////////////////////////////////////////////////// -// Functions -//////////////////////////////////////////////////////////////////////////////// - -//////////////////////////////////////////////////////////////////////////////// -/** -* Get full Device Parameter configuration table -* -* @return pointer to configuration table -*/ -//////////////////////////////////////////////////////////////////////////////// -const void * par_cfg_get_table(void) -{ - return &g_par_table; -} - -//////////////////////////////////////////////////////////////////////////////// -/** -* Get single Device Parameter configuration -* -* @return pointer to parameter config -*/ -//////////////////////////////////////////////////////////////////////////////// -const void * par_cfg_get(const par_num_t par_num) -{ - return &g_par_table[par_num]; -} - -//////////////////////////////////////////////////////////////////////////////// -/** -* Get configuration table size in bytes -* -* @return gu32_par_table_size - Size of table in bytes -*/ -//////////////////////////////////////////////////////////////////////////////// -uint32_t par_cfg_get_table_size(void) -{ - return gu32_par_table_size; -} - -//////////////////////////////////////////////////////////////////////////////// -/** -* @} -*/ -//////////////////////////////////////////////////////////////////////////////// diff --git a/template/par_cfg.htmp b/template/par_cfg.htmp deleted file mode 100644 index 454c8cb..0000000 --- a/template/par_cfg.htmp +++ /dev/null @@ -1,342 +0,0 @@ -// Copyright (c) 2026 Ziga Miklosic -// All Rights Reserved -// This software is under MIT licence (https://opensource.org/licenses/MIT) -//////////////////////////////////////////////////////////////////////////////// -/** -*@file par_cfg.h -*@brief Configuration for device parameters -*@author Ziga Miklosic -*@email ziga.miklosic@gmail.com -*@date 29.01.2026 -*@version V3.0.1 -*/ -//////////////////////////////////////////////////////////////////////////////// -/** -*@addtogroup PAR_CFG -* @{ -*/ -//////////////////////////////////////////////////////////////////////////////// - -#ifndef _PAR_CFG_H_ -#define _PAR_CFG_H_ - -//////////////////////////////////////////////////////////////////////////////// -// Includes -//////////////////////////////////////////////////////////////////////////////// -#include - -// USER CODE BEGIN... - -#include "config/proj_cfg.h" - -// Debug communication port -#include "middleware/cli/cli/src/cli.h" - -// NVM -//#include "middleware/nvm/nvm_cfg.h" - -// USER CODE END... - -//////////////////////////////////////////////////////////////////////////////// -// Definitions -//////////////////////////////////////////////////////////////////////////////// - - -/** - * List of device parameters - * - * @note User shall provide parameter name here as it would be using - * later inside code. - * - * @note User shall change code only inside section of "USER_CODE_BEGIN" - * ans "USER_CODE_END". - * - * Must be started with 0! - */ -enum -{ - // USER CODE START... - - // Channel 1 control - ePAR_CH1_CTRL = 0, - ePAR_CH1_AFE_MEAS_EN, - ePAR_CH1_TEST_MODE_EN, - ePAR_CH1_REF_SEL, - ePAR_CH1_REF_VAL, - - // Channel 1 status - ePAR_CH1_STATUS, - ePAR_CH1_FSM_STATE, - - // Channel 1 reference values - ePAR_CH1_TSIM, - ePAR_CH1_RSIM, - ePAR_CH1_VOUT, - ePAR_CH1_ISINK, - ePAR_CH1_VSET, - ePAR_CH1_VTH, - ePAR_CH1_VDAC_C, - ePAR_CH1_VDAC_F, - ePAR_CH1_VDAC_C_RAW, - ePAR_CH1_VDAC_F_RAW, - - // Channel 1 measurements - ePAR_CH1_CUR, - ePAR_CH1_VOL, - ePAR_CH1_CUR_RAW, - ePAR_CH1_VOL_RAW, - ePAR_CH1_AFE_VCC, - ePAR_CH1_AFE_RES, - - // Channel 2 control - ePAR_CH2_CTRL, - ePAR_CH2_AFE_MEAS_EN, - ePAR_CH2_TEST_MODE_EN, - ePAR_CH2_REF_SEL, - ePAR_CH2_REF_VAL, - - // Channel 2 status - ePAR_CH2_STATUS, - ePAR_CH2_FSM_STATE, - - // Channel 2 reference values - ePAR_CH2_TSIM, - ePAR_CH2_RSIM, - ePAR_CH2_VOUT, - ePAR_CH2_ISINK, - ePAR_CH2_VSET, - ePAR_CH2_VTH, - ePAR_CH2_VDAC_C, - ePAR_CH2_VDAC_F, - ePAR_CH2_VDAC_C_RAW, - ePAR_CH2_VDAC_F_RAW, - - // Channel 2 measurements - ePAR_CH2_CUR, - ePAR_CH2_VOL, - ePAR_CH2_CUR_RAW, - ePAR_CH2_VOL_RAW, - ePAR_CH2_AFE_VCC, - ePAR_CH2_AFE_RES, - - // Channel 3 control - ePAR_CH3_CTRL, - ePAR_CH3_AFE_MEAS_EN, - ePAR_CH3_TEST_MODE_EN, - ePAR_CH3_REF_SEL, - ePAR_CH3_REF_VAL, - - // Channel 3 status - ePAR_CH3_STATUS, - ePAR_CH3_FSM_STATE, - - // Channel 3 reference values - ePAR_CH3_TSIM, - ePAR_CH3_RSIM, - ePAR_CH3_VOUT, - ePAR_CH3_ISINK, - ePAR_CH3_VSET, - ePAR_CH3_VTH, - ePAR_CH3_VDAC_C, - ePAR_CH3_VDAC_F, - ePAR_CH3_VDAC_C_RAW, - ePAR_CH3_VDAC_F_RAW, - - // Channel 3 measurements - ePAR_CH3_CUR, - ePAR_CH3_VOL, - ePAR_CH3_CUR_RAW, - ePAR_CH3_VOL_RAW, - ePAR_CH3_AFE_VCC, - ePAR_CH3_AFE_RES, - - // Channel 4 control - ePAR_CH4_CTRL, - ePAR_CH4_AFE_MEAS_EN, - ePAR_CH4_TEST_MODE_EN, - ePAR_CH4_REF_SEL, - ePAR_CH4_REF_VAL, - - // Channel 4 status - ePAR_CH4_STATUS, - ePAR_CH4_FSM_STATE, - - // Channel 4 reference values - ePAR_CH4_TSIM, - ePAR_CH4_RSIM, - ePAR_CH4_VOUT, - ePAR_CH4_ISINK, - ePAR_CH4_VSET, - ePAR_CH4_VTH, - ePAR_CH4_VDAC_C, - ePAR_CH4_VDAC_F, - ePAR_CH4_VDAC_C_RAW, - ePAR_CH4_VDAC_F_RAW, - - // Channel 4 measurements - ePAR_CH4_CUR, - ePAR_CH4_VOL, - ePAR_CH4_CUR_RAW, - ePAR_CH4_VOL_RAW, - ePAR_CH4_AFE_VCC, - ePAR_CH4_AFE_RES, - - // System status - ePAR_SYS_STATUS, - ePAR_SYS_CPU_LOAD, - ePAR_SYS_CPU_LOAD_MAX, - - // USER CODE END... - - ePAR_NUM_OF -}; -typedef uint16_t par_num_t; - -// USER CODE START... - -/** - * Parameter enumeration gap between channels - */ -#define PAR_CH_ENUM_GAP ( ePAR_CH2_CTRL - ePAR_CH1_CTRL ) - -/** - * Get channel parameter enumeration - */ -#define PAR_GET_BY_CH(par,ch) ( par + ( PAR_CH_ENUM_GAP * ch )) - -/** - * Temperature simulation channel control - */ -enum -{ - ePAR_CH_CTRL_NORMAL = 0, /** -* -* Interface layer for device parameters -* -* Put code that is platform depended inside code block start with -* "USER_CODE_BEGIN" and with end of "USER_CODE_END". -*/ -//////////////////////////////////////////////////////////////////////////////// - -//////////////////////////////////////////////////////////////////////////////// -// Includes -//////////////////////////////////////////////////////////////////////////////// -#include "par_if.h" - -// USER INCLUDES BEGIN... - -#include "common/utils/src/utils.h" -#include "cmsis_os2.h" - -// USER INCLUDES END... - -//////////////////////////////////////////////////////////////////////////////// -// Definitions -//////////////////////////////////////////////////////////////////////////////// - -// USER DEFINITIONS BEGIN... - -/** - * Parameter mutex timeout - * - * Unit: ms - */ -#define PAR_CFG_MUTEX_TIMEOUT_MS ( 10 ) - -// USER DEFINITIONS END... - -//////////////////////////////////////////////////////////////////////////////// -// Variables -//////////////////////////////////////////////////////////////////////////////// - -// USER VARIABLES BEGIN... - -/** - * Parameters OS mutex - */ -static osMutexId_t g_par_mutex_id = NULL; -const osMutexAttr_t g_par_mutex_attr = -{ - .name = "par", - .attr_bits = ( osMutexPrioInherit ), -}; - -// USER VARIABLES END... - -//////////////////////////////////////////////////////////////////////////////// -// Functions -//////////////////////////////////////////////////////////////////////////////// - -//////////////////////////////////////////////////////////////////////////////// -/** -* Initialize low level interface -* -* @note User shall provide definition of that function based on used platform! -* -* @return status - Status of initialization -*/ -//////////////////////////////////////////////////////////////////////////////// -par_status_t par_if_init(void) -{ - par_status_t status = ePAR_OK; - - // USER CODE BEGIN... - - // Create mutex - g_par_mutex_id = osMutexNew( &g_par_mutex_attr ); - - if ( NULL == g_par_mutex_id ) - { - status = ePAR_ERROR; - } - - // USER CODE END... - - - return status; -} - -//////////////////////////////////////////////////////////////////////////////// -/** -* Acquire mutex for specified parameter -* -* @note User shall provide definition of that function based on used platform! -* -* If mutex is not needed leave empty space between user code begin and end. -* -* @param[in] par_num - Parameter number (enumeration) -* @return status - Status of operation -*/ -//////////////////////////////////////////////////////////////////////////////// -par_status_t par_if_aquire_mutex(const par_num_t par_num) -{ - par_status_t status = ePAR_OK; - - // USER CODE BEGIN... - UNUSED(par_num); - if ( osOK == osMutexAcquire( g_par_mutex_id, PAR_CFG_MUTEX_TIMEOUT_MS )) - { - // No action - } - else - { - status = ePAR_ERROR; - } - - // USER CODE END... - - return status; -} - -//////////////////////////////////////////////////////////////////////////////// -/** -* Release mutex for specified parameter -* -* @note User shall provide definition of that function based on used platform! -* -* If mutex is not needed leave empty space between user code begin and end. -* -* @param[in] par_num - Parameter number (enumeration) -* @return status - Status of operation -*/ -//////////////////////////////////////////////////////////////////////////////// -void par_if_release_mutex(const par_num_t par_num) -{ - // USER CODE BEGIN... - UNUSED(par_num); - osMutexRelease( g_par_mutex_id ); - - // USER CODE END... -} - -//////////////////////////////////////////////////////////////////////////////// -/** -* Calculate hash -* -* @note User shall provide definition of that function based on used platform! -* -* If not being used leave empty. -* -* This function does not have an affect if "PAR_CFG_TABLE_ID_CHECK_EN" -* is set to 0. -* -* @param[in] p_data - Pointer to data for hash calculation -* @param[in] size - Size of data in bytes -* @return p_hash - Pointer to calculated hash number -*/ -//////////////////////////////////////////////////////////////////////////////// -void par_if_calc_hash(const uint8_t * const p_data, const uint32_t size, uint8_t * const p_hash) -{ - UNUSED( p_data ); - UNUSED( p_hash ); - UNUSED( size ); - - // USER CODE BEGIN... - - // USER CODE END... -} - -//////////////////////////////////////////////////////////////////////////////// -/** -* @} -*/ -//////////////////////////////////////////////////////////////////////////////// diff --git a/template/par_if.htmp b/template/par_if.htmp deleted file mode 100644 index 635e091..0000000 --- a/template/par_if.htmp +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (c) 2026 Ziga Miklosic -// All Rights Reserved -// This software is under MIT licence (https://opensource.org/licenses/MIT) -//////////////////////////////////////////////////////////////////////////////// -/** -*@file par_if.h -*@brief Interface for device parameters -*@author Ziga Miklosic -*@email ziga.miklosic@gmail.com -*@date 29.01.2026 -*@version V3.0.1 -*/ -//////////////////////////////////////////////////////////////////////////////// -/** -*@addtogroup PAR_IF -* @{ -*/ -//////////////////////////////////////////////////////////////////////////////// - -#ifndef _PAR_IF_H_ -#define _PAR_IF_H_ - -//////////////////////////////////////////////////////////////////////////////// -// Includes -//////////////////////////////////////////////////////////////////////////////// -#include -#include "parameters/src/par.h" -#include "par_cfg.h" - -//////////////////////////////////////////////////////////////////////////////// -// Definitions -//////////////////////////////////////////////////////////////////////////////// - -//////////////////////////////////////////////////////////////////////////////// -// Functions Prototypes -//////////////////////////////////////////////////////////////////////////////// -par_status_t par_if_init (void); -par_status_t par_if_aquire_mutex (const par_num_t par_num); -void par_if_release_mutex (const par_num_t par_num); -void par_if_calc_hash (const uint8_t * const p_data, const uint32_t size, uint8_t * const p_hash); - -#endif // _PAR_IF_H_ diff --git a/template/par_layout_static.htmp b/template/par_layout_static.htmp new file mode 100644 index 0000000..3fee5e9 --- /dev/null +++ b/template/par_layout_static.htmp @@ -0,0 +1,47 @@ +/** + * @file par_layout_static.h + * @brief Declare the generated static layout contract. + * @author wdfk-prog () + * @version 1.0 + * @date 2026-03-21 + * + * @copyright Copyright (c) 2026 + * + * @note : + * @par Change Log: + * Date Version Author Description + * 2026-03-21 1.0 wdfk-prog first version + */ +#ifndef _PAR_LAYOUT_STATIC_H_ +#define _PAR_LAYOUT_STATIC_H_ + +#include + +#include "def/par_def.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Static layout contract (template) + * + * @note Script layout mode expects generated values for: + * - PAR_LAYOUT_STATIC_COUNT8/16/32 + * - PAR_LAYOUT_STATIC_OFFSET_TABLE (points to a const offset table) + */ +#define PAR_LAYOUT_STATIC_COUNT8 (0u) +#define PAR_LAYOUT_STATIC_COUNT16 (0u) +#define PAR_LAYOUT_STATIC_COUNT32 (0u) + +/** + * Static offset map generated by external script. + */ +extern const uint16_t g_par_layout_static_offset[ePAR_NUM_OF]; +#define PAR_LAYOUT_STATIC_OFFSET_TABLE (g_par_layout_static_offset) + +#ifdef __cplusplus +} +#endif + +#endif // _PAR_LAYOUT_STATIC_H_ diff --git a/template/par_table.deftmp b/template/par_table.deftmp new file mode 100644 index 0000000..77e1a78 --- /dev/null +++ b/template/par_table.deftmp @@ -0,0 +1,191 @@ +/* + * Parameter single-source list for X-Macro expansion. + * + * This file is intentionally included multiple times. + * Do not add include guards and do not place executable code here. + * + * Supported item macros: + * PAR_ITEM_U8 (enum_, id_, name_, min_, max_, def_, unit_, access_, read_roles_, write_roles_, pers_, desc_) + * PAR_ITEM_U16(enum_, id_, name_, min_, max_, def_, unit_, access_, read_roles_, write_roles_, pers_, desc_) + * PAR_ITEM_U32(enum_, id_, name_, min_, max_, def_, unit_, access_, read_roles_, write_roles_, pers_, desc_) + * PAR_ITEM_I8 (enum_, id_, name_, min_, max_, def_, unit_, access_, read_roles_, write_roles_, pers_, desc_) + * PAR_ITEM_I16(enum_, id_, name_, min_, max_, def_, unit_, access_, read_roles_, write_roles_, pers_, desc_) + * PAR_ITEM_I32(enum_, id_, name_, min_, max_, def_, unit_, access_, read_roles_, write_roles_, pers_, desc_) + * PAR_ITEM_F32(enum_, id_, name_, min_, max_, def_, unit_, access_, read_roles_, write_roles_, pers_, desc_) + * + * Field description: + * enum_ - Parameter enum index (used as designated initializer index) + * id_ - Unique external parameter ID + * name_ - Parameter display name + * min_ - Minimum allowed value + * max_ - Maximum allowed value + * def_ - Default value, must be within [min_, max_] + * unit_ - Engineering unit string, or NULL if not applicable + * access_ - External access capability mask: ePAR_ACCESS_RO / ePAR_ACCESS_RW + * read_roles_ - External role mask allowed to read this parameter + * write_roles_ - External role mask allowed to write this parameter + * pers_ - Persistence flag: true if stored to NVM, otherwise false + * desc_ - Human-readable description + * + * Formatting note: + * Keep items grouped by subsystem/channel and aligned for readability. + */ + + +/* ============================================================================================================================================================= */ +/* enum_ id_ name_ min_ max_ def_ unit_ access_ read_roles_ write_roles_ pers_ desc_ */ +/* ============================================================================================================================================================= */ + + +/* ============================================================================================================================= */ +/* CHANNEL 1 */ +/* ============================================================================================================================= */ + +/* Channel 1 control */ +PAR_ITEM_U8 (ePAR_CH1_CTRL , 0 , "Ch1 Control" , 0U , 2U , 2U , NULL , ePAR_ACCESS_RW, ePAR_ROLE_ALL, ePAR_ROLE_ALL , false, "Channel 1 control: 0-Normal | 1-Short | 2-Open. NOTE: If status is then it will force to Open!") +PAR_ITEM_U8 (ePAR_CH1_AFE_MEAS_EN , 1 , "Ch1 AFE Measurement Control", 0U , 1U , 1U , NULL , ePAR_ACCESS_RW, ePAR_ROLE_ALL, ePAR_ROLE_ALL , true , "Channel 1 control AFE measurement usage for calculations: 0-Disable | 1-Enable") +PAR_ITEM_U8 (ePAR_CH1_TEST_MODE_EN, 2 , "Ch1 Test Mode" , 0U , 1U , 0U , NULL , ePAR_ACCESS_RW, ePAR_ROLE_ALL, ePAR_ROLE_ALL , false, "Channel 1 test mode enable: 0-Disable | 1-Enable. NOTE: This will put channel to Normal state - ignoring detection logic!") +PAR_ITEM_U8 (ePAR_CH1_REF_SEL , 3 , "Ch1 Reference Selection" , 0U , 4U , 0U , NULL , ePAR_ACCESS_RW, ePAR_ROLE_ALL, ePAR_ROLE_ALL , true , "Channel 1 reference selection: 0-Temperature | 1-Resistance | 2-Vout | 3-Isink | 4-Vset") +PAR_ITEM_F32 (ePAR_CH1_REF_VAL , 4 , "Ch1 Reference Value" , -1E6f , 1E6f , 0.0f , NULL , ePAR_ACCESS_RW, ePAR_ROLE_ALL, ePAR_ROLE_ALL , true , "Channel 1 reference value based on control variable set") + +/* Channel 1 status */ +PAR_ITEM_U8 (ePAR_CH1_STATUS , 10 , "Ch1 Status" , 0U , 3U , 3U , NULL , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 1 status. 0-Normal | 1-Short | 2-Open | 3-Not Connected") +PAR_ITEM_U8 (ePAR_CH1_FSM_STATE , 11 , "Ch1 FSM State" , 0U , 3U , 0U , NULL , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 1 FSM state. 0-Idle | 1-Measure Vcc | 2-Measure Res | 3-Normal") + +/* Channel 1 reference values */ +PAR_ITEM_F32 (ePAR_CH1_TSIM , 21 , "Ch1 Ref Temperature" , -20.0f , 350.0f , 0.0f , "degC", ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 1 temperature in degree C") +PAR_ITEM_F32 (ePAR_CH1_RSIM , 22 , "Ch1 Ref Resistance" , 920.0f , 2300.0f , 920.0f , "ohm" , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 1 resistance reference in ohm") +PAR_ITEM_F32 (ePAR_CH1_VOUT , 23 , "Ch1 Ref Vout" , 0.85f , 1.58f , 0.85f , "V" , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 1 output voltage in V") +PAR_ITEM_F32 (ePAR_CH1_ISINK , 24 , "Ch1 Ref Isink" , 658.5f , 950.0f , 658.5f , "uA" , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 1 sink current reference in uA") +PAR_ITEM_F32 (ePAR_CH1_VSET , 25 , "Ch1 Ref Vset" , 1.7955f, 2.548f , 1.7955f, "V" , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 1 Vset (reference) voltage in V") +PAR_ITEM_F32 (ePAR_CH1_VTH , 26 , "Ch1 Ref Vth" , 0.0f , 3.3f , 0.0f , "V" , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 1 Vth (thevenin) voltage in V. Equivalent circuit from both DAC combined.") +PAR_ITEM_F32 (ePAR_CH1_VDAC_C , 27 , "Ch1 Ref Vdac Coarse" , 0.0f , 3.3f , 0.0f , "V" , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 1 Vdac coarse voltage in V") +PAR_ITEM_F32 (ePAR_CH1_VDAC_F , 28 , "Ch1 Ref Vdac Fine" , 0.0f , 3.3f , 0.0f , "V" , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 1 Vdac fine voltage in V") +PAR_ITEM_U16 (ePAR_CH1_VDAC_F_RAW , 29 , "Ch1 Ref Raw DAC Fine" , 0U , 4095U , 0U , NULL , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 1 raw fine DAC value") +PAR_ITEM_U16 (ePAR_CH1_VDAC_C_RAW , 30 , "Ch1 Ref Raw DAC Coarse" , 0U , 4095U , 0U , NULL , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 1 raw coarse DAC value") + +/* Channel 1 measurements */ +PAR_ITEM_F32 (ePAR_CH1_CUR , 50 , "Ch1 Act Isink" , 0.0f , 5000.0f , 0.0f , "uA" , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 1 actual current sink value in uA") +PAR_ITEM_F32 (ePAR_CH1_VOL , 52 , "Ch1 Act Vout" , 0.0f , 10.0f , 0.0f , "V" , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 1 actual output voltage value in V") +PAR_ITEM_U16 (ePAR_CH1_CUR_RAW , 53 , "Ch1 Raw Isink" , 0U , 8191U , 0U , NULL , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 1 raw ADC current value") +PAR_ITEM_U16 (ePAR_CH1_VOL_RAW , 54 , "Ch1 Raw Vout" , 0U , 8191U , 0U , NULL , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 1 raw ADC voltage value") +PAR_ITEM_F32 (ePAR_CH1_AFE_VCC , 55 , "Ch1 AFE Vcc" , 0.0f , 10.0f , 0.0f , "V" , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 1 measured AFE Vcc voltage value in V") +PAR_ITEM_F32 (ePAR_CH1_AFE_RES , 56 , "Ch1 AFE Res" , 0.0f , 5.0e3f , 0.0f , "ohm" , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 1 measured AFE resistance (pull-up + series) value in ohm") + + +/* ============================================================================================================================= */ +/* CHANNEL 2 */ +/* ============================================================================================================================= */ + +/* Channel 2 control */ +PAR_ITEM_U8 (ePAR_CH2_CTRL , 100 , "Ch2 Control" , 0U , 2U , 2U , NULL , ePAR_ACCESS_RW, ePAR_ROLE_ALL, ePAR_ROLE_ALL , false, "Channel 2 control: 0-Normal | 1-Short | 2-Open. NOTE: If status is then it will force to Open!") +PAR_ITEM_U8 (ePAR_CH2_AFE_MEAS_EN , 101 , "Ch2 AFE Measurement Control", 0U , 1U , 1U , NULL , ePAR_ACCESS_RW, ePAR_ROLE_ALL, ePAR_ROLE_ALL , true , "Channel 2 control AFE measurement usage for calculations: 0-Disable | 1-Enable") +PAR_ITEM_U8 (ePAR_CH2_TEST_MODE_EN, 102 , "Ch2 Test Mode" , 0U , 1U , 0U , NULL , ePAR_ACCESS_RW, ePAR_ROLE_ALL, ePAR_ROLE_ALL , false, "Channel 2 test mode enable: 0-Disable | 1-Enable. NOTE: This will put channel to Normal state - ignoring detection logic!") +PAR_ITEM_U8 (ePAR_CH2_REF_SEL , 103 , "Ch2 Reference Selection" , 0U , 4U , 0U , NULL , ePAR_ACCESS_RW, ePAR_ROLE_ALL, ePAR_ROLE_ALL , true , "Channel 2 reference selection: 0-Temperature | 1-Resistance | 2-Vout | 3-Isink | 4-Vset") +PAR_ITEM_F32 (ePAR_CH2_REF_VAL , 104 , "Ch2 Reference Value" , -1E6f , 1E6f , 0.0f , NULL , ePAR_ACCESS_RW, ePAR_ROLE_ALL, ePAR_ROLE_ALL , true , "Channel 2 reference value based on control variable set") + +/* Channel 2 status */ +PAR_ITEM_U8 (ePAR_CH2_STATUS , 111 , "Ch2 Status" , 0U , 3U , 3U , NULL , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 2 status. 0-Normal | 1-Short | 2-Open | 3-Not Connected") +PAR_ITEM_U8 (ePAR_CH2_FSM_STATE , 112 , "Ch2 FSM State" , 0U , 3U , 0U , NULL , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 2 FSM state. 0-Idle | 1-Measure Vcc | 2-Measure Res | 3-Normal") + +/* Channel 2 reference values */ +PAR_ITEM_F32 (ePAR_CH2_TSIM , 120 , "Ch2 Ref Temperature" , -20.0f , 350.0f , 0.0f , "degC", ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 2 temperature in degree C") +PAR_ITEM_F32 (ePAR_CH2_RSIM , 121 , "Ch2 Ref Resistance" , 920.0f , 2300.0f , 920.0f , "ohm" , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 2 resistance reference in ohm") +PAR_ITEM_F32 (ePAR_CH2_VOUT , 122 , "Ch2 Ref Vout" , 0.85f , 1.58f , 0.85f , "V" , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 2 output voltage in V") +PAR_ITEM_F32 (ePAR_CH2_ISINK , 123 , "Ch2 Ref Isink" , 658.5f , 950.0f , 658.5f , "uA" , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 2 sink current reference in uA") +PAR_ITEM_F32 (ePAR_CH2_VSET , 124 , "Ch2 Ref Vset" , 1.7955f, 2.548f , 1.7955f, "V" , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 2 Vset (reference) voltage in V") +PAR_ITEM_F32 (ePAR_CH2_VTH , 125 , "Ch2 Ref Vth" , 0.0f , 3.3f , 0.0f , "V" , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 2 Vth (thevenin) voltage in V. Equivalent circuit from both DAC combined.") +PAR_ITEM_F32 (ePAR_CH2_VDAC_C , 126 , "Ch2 Ref Vdac Coarse" , 0.0f , 3.3f , 0.0f , "V" , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 2 Vdac coarse voltage in V") +PAR_ITEM_F32 (ePAR_CH2_VDAC_F , 127 , "Ch2 Ref Vdac Fine" , 0.0f , 3.3f , 0.0f , "V" , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 2 Vdac fine voltage in V") +PAR_ITEM_U16 (ePAR_CH2_VDAC_F_RAW , 128 , "Ch2 Ref Raw DAC Fine" , 0U , 4095U , 0U , NULL , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 2 raw fine DAC value") +PAR_ITEM_U16 (ePAR_CH2_VDAC_C_RAW , 129 , "Ch2 Ref Raw DAC Coarse" , 0U , 4095U , 0U , NULL , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 2 raw coarse DAC value") + +/* Channel 2 measurements */ +PAR_ITEM_F32 (ePAR_CH2_CUR , 150 , "Ch2 Act Isink" , 0.0f , 5000.0f , 0.0f , "uA" , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 2 actual current sink value in uA") +PAR_ITEM_F32 (ePAR_CH2_VOL , 151 , "Ch2 Act Vout" , 0.0f , 10.0f , 0.0f , "V" , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 2 actual output voltage value in V") +PAR_ITEM_U16 (ePAR_CH2_CUR_RAW , 152 , "Ch2 Raw Isink" , 0U , 8191U , 0U , NULL , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 2 raw ADC current value") +PAR_ITEM_U16 (ePAR_CH2_VOL_RAW , 153 , "Ch2 Raw Vout" , 0U , 8191U , 0U , NULL , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 2 raw ADC voltage value") +PAR_ITEM_F32 (ePAR_CH2_AFE_VCC , 154 , "Ch2 AFE Vcc" , 0.0f , 10.0f , 0.0f , "V" , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 2 measured AFE Vcc voltage value in V") +PAR_ITEM_F32 (ePAR_CH2_AFE_RES , 155 , "Ch2 AFE Res" , 0.0f , 5.0e3f , 0.0f , "ohm" , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 2 measured AFE resistance (pull-up + series) value in ohm") + + +/* ============================================================================================================================= */ +/* CHANNEL 3 */ +/* ============================================================================================================================= */ + +/* Channel 3 control */ +PAR_ITEM_U8 (ePAR_CH3_CTRL , 200 , "Ch3 Control" , 0U , 2U , 2U , NULL , ePAR_ACCESS_RW, ePAR_ROLE_ALL, ePAR_ROLE_ALL , false, "Channel 3 control: 0-Normal | 1-Short | 2-Open. NOTE: If status is then it will force to Open!") +PAR_ITEM_U8 (ePAR_CH3_AFE_MEAS_EN , 201 , "Ch3 AFE Measurement Control", 0U , 1U , 1U , NULL , ePAR_ACCESS_RW, ePAR_ROLE_ALL, ePAR_ROLE_ALL , true , "Channel 3 control AFE measurement usage for calculations: 0-Disable | 1-Enable") +PAR_ITEM_U8 (ePAR_CH3_TEST_MODE_EN, 202 , "Ch3 Test Mode" , 0U , 1U , 0U , NULL , ePAR_ACCESS_RW, ePAR_ROLE_ALL, ePAR_ROLE_ALL , false, "Channel 3 test mode enable: 0-Disable | 1-Enable. NOTE: This will put channel to Normal state - ignoring detection logic!") +PAR_ITEM_U8 (ePAR_CH3_REF_SEL , 203 , "Ch3 Reference Selection" , 0U , 4U , 0U , NULL , ePAR_ACCESS_RW, ePAR_ROLE_ALL, ePAR_ROLE_ALL , true , "Channel 3 reference selection: 0-Temperature | 1-Resistance | 2-Vout | 3-Isink | 4-Vset") +PAR_ITEM_F32 (ePAR_CH3_REF_VAL , 204 , "Ch3 Reference Value" , -1E6f , 1E6f , 0.0f , NULL , ePAR_ACCESS_RW, ePAR_ROLE_ALL, ePAR_ROLE_ALL , true , "Channel 3 reference value based on control variable set") + +/* Channel 3 status */ +PAR_ITEM_U8 (ePAR_CH3_STATUS , 210 , "Ch3 Status" , 0U , 3U , 3U , NULL , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 3 status. 0-Normal | 1-Short | 2-Open | 3-Not Connected") +PAR_ITEM_U8 (ePAR_CH3_FSM_STATE , 211 , "Ch3 FSM State" , 0U , 3U , 0U , NULL , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 3 FSM state. 0-Idle | 1-Measure Vcc | 2-Measure Res | 3-Normal") + +/* Channel 3 reference values */ +PAR_ITEM_F32 (ePAR_CH3_TSIM , 220 , "Ch3 Ref Temperature" , -20.0f , 350.0f , 0.0f , "degC", ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 3 temperature in degree C") +PAR_ITEM_F32 (ePAR_CH3_RSIM , 221 , "Ch3 Ref Resistance" , 920.0f , 2300.0f , 920.0f , "ohm" , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 3 resistance reference in ohm") +PAR_ITEM_F32 (ePAR_CH3_VOUT , 222 , "Ch3 Ref Vout" , 0.85f , 1.58f , 0.85f , "V" , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 3 output voltage in V") +PAR_ITEM_F32 (ePAR_CH3_ISINK , 223 , "Ch3 Ref Isink" , 658.5f , 950.0f , 658.5f , "uA" , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 3 sink current reference in uA") +PAR_ITEM_F32 (ePAR_CH3_VSET , 224 , "Ch3 Ref Vset" , 1.7955f, 2.548f , 1.7955f, "V" , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 3 Vset (reference) voltage in V") +PAR_ITEM_F32 (ePAR_CH3_VTH , 225 , "Ch3 Ref Vth" , 0.0f , 3.3f , 0.0f , "V" , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 3 Vth (thevenin) voltage in V. Equivalent circuit from both DAC combined.") +PAR_ITEM_F32 (ePAR_CH3_VDAC_C , 226 , "Ch3 Ref Vdac Coarse" , 0.0f , 3.3f , 0.0f , "V" , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 3 Vdac coarse voltage in V") +PAR_ITEM_F32 (ePAR_CH3_VDAC_F , 227 , "Ch3 Ref Vdac Fine" , 0.0f , 3.3f , 0.0f , "V" , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 3 Vdac fine voltage in V") +PAR_ITEM_U16 (ePAR_CH3_VDAC_F_RAW , 228 , "Ch3 Ref Raw DAC Fine" , 0U , 4095U , 0U , NULL , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 3 raw fine DAC value") +PAR_ITEM_U16 (ePAR_CH3_VDAC_C_RAW , 229 , "Ch3 Ref Raw DAC Coarse" , 0U , 4095U , 0U , NULL , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 3 raw coarse DAC value") + +/* Channel 3 measurements */ +PAR_ITEM_F32 (ePAR_CH3_CUR , 250 , "Ch3 Act Isink" , 0.0f , 5000.0f , 0.0f , "uA" , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 3 actual current sink value in uA") +PAR_ITEM_F32 (ePAR_CH3_VOL , 251 , "Ch3 Act Vout" , 0.0f , 10.0f , 0.0f , "V" , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 3 actual output voltage value in V") +PAR_ITEM_U16 (ePAR_CH3_CUR_RAW , 252 , "Ch3 Raw Isink" , 0U , 8191U , 0U , NULL , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 3 raw ADC current value") +PAR_ITEM_U16 (ePAR_CH3_VOL_RAW , 253 , "Ch3 Raw Vout" , 0U , 8191U , 0U , NULL , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 3 raw ADC voltage value") +PAR_ITEM_F32 (ePAR_CH3_AFE_VCC , 254 , "Ch3 AFE Vcc" , 0.0f , 10.0f , 0.0f , "V" , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 3 measured AFE Vcc voltage value in V") +PAR_ITEM_F32 (ePAR_CH3_AFE_RES , 255 , "Ch3 AFE Res" , 0.0f , 5.0e3f , 0.0f , "ohm" , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 3 measured AFE resistance (pull-up + series) value in ohm") + + +/* ============================================================================================================================= */ +/* CHANNEL 4 */ +/* ============================================================================================================================= */ + +/* Channel 4 control */ +PAR_ITEM_U8 (ePAR_CH4_CTRL , 300 , "Ch4 Control" , 0U , 2U , 2U , NULL , ePAR_ACCESS_RW, ePAR_ROLE_ALL, ePAR_ROLE_ALL , false, "Channel 4 control: 0-Normal | 1-Short | 2-Open. NOTE: If status is then it will force to Open!") +PAR_ITEM_U8 (ePAR_CH4_AFE_MEAS_EN , 301 , "Ch4 AFE Measurement Control", 0U , 1U , 1U , NULL , ePAR_ACCESS_RW, ePAR_ROLE_ALL, ePAR_ROLE_ALL , true , "Channel 4 control AFE measurement usage for calculations: 0-Disable | 1-Enable") +PAR_ITEM_U8 (ePAR_CH4_TEST_MODE_EN, 302 , "Ch4 Test Mode" , 0U , 1U , 0U , NULL , ePAR_ACCESS_RW, ePAR_ROLE_ALL, ePAR_ROLE_ALL , false, "Channel 4 test mode enable: 0-Disable | 1-Enable. NOTE: This will put channel to Normal state - ignoring detection logic!") +PAR_ITEM_U8 (ePAR_CH4_REF_SEL , 303 , "Ch4 Reference Selection" , 0U , 4U , 0U , NULL , ePAR_ACCESS_RW, ePAR_ROLE_ALL, ePAR_ROLE_ALL , true , "Channel 4 reference selection: 0-Temperature | 1-Resistance | 2-Vout | 3-Isink | 4-Vset") +PAR_ITEM_F32 (ePAR_CH4_REF_VAL , 304 , "Ch4 Reference Value" , -1E6f , 1E6f , 0.0f , NULL , ePAR_ACCESS_RW, ePAR_ROLE_ALL, ePAR_ROLE_ALL , true , "Channel 4 reference value based on control variable set") + +/* Channel 4 status */ +PAR_ITEM_U8 (ePAR_CH4_STATUS , 310 , "Ch4 Status" , 0U , 3U , 3U , NULL , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 4 status. 0-Normal | 1-Short | 2-Open | 3-Not Connected") +PAR_ITEM_U8 (ePAR_CH4_FSM_STATE , 311 , "Ch4 FSM State" , 0U , 3U , 0U , NULL , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 4 FSM state. 0-Idle | 1-Measure Vcc | 2-Measure Res | 3-Normal") + +/* Channel 4 reference values */ +PAR_ITEM_F32 (ePAR_CH4_TSIM , 320 , "Ch4 Ref Temperature" , -20.0f , 350.0f , 0.0f , "degC", ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 4 temperature in degree C") +PAR_ITEM_F32 (ePAR_CH4_RSIM , 321 , "Ch4 Ref Resistance" , 920.0f , 2300.0f , 920.0f , "ohm" , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 4 resistance reference in ohm") +PAR_ITEM_F32 (ePAR_CH4_VOUT , 322 , "Ch4 Ref Vout" , 0.85f , 1.58f , 0.85f , "V" , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 4 output voltage in V") +PAR_ITEM_F32 (ePAR_CH4_ISINK , 323 , "Ch4 Ref Isink" , 658.5f , 950.0f , 658.5f , "uA" , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 4 sink current reference in uA") +PAR_ITEM_F32 (ePAR_CH4_VSET , 324 , "Ch4 Ref Vset" , 1.7955f, 2.548f , 1.7955f, "V" , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 4 Vset (reference) voltage in V") +PAR_ITEM_F32 (ePAR_CH4_VTH , 325 , "Ch4 Ref Vth" , 0.0f , 3.3f , 0.0f , "V" , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 4 Vth (thevenin) voltage in V. Equivalent circuit from both DAC combined.") +PAR_ITEM_F32 (ePAR_CH4_VDAC_C , 326 , "Ch4 Ref Vdac Coarse" , 0.0f , 3.3f , 0.0f , "V" , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 4 Vdac coarse voltage in V") +PAR_ITEM_F32 (ePAR_CH4_VDAC_F , 327 , "Ch4 Ref Vdac Fine" , 0.0f , 3.3f , 0.0f , "V" , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 4 Vdac fine voltage in V") +PAR_ITEM_U16 (ePAR_CH4_VDAC_F_RAW , 328 , "Ch4 Ref Raw DAC Fine" , 0U , 4095U , 0U , NULL , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 4 raw fine DAC value") +PAR_ITEM_U16 (ePAR_CH4_VDAC_C_RAW , 329 , "Ch4 Ref Raw DAC Coarse" , 0U , 4095U , 0U , NULL , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 4 raw coarse DAC value") + +/* Channel 4 measurements */ +PAR_ITEM_F32 (ePAR_CH4_CUR , 360 , "Ch4 Act Isink" , 0.0f , 5000.0f , 0.0f , "uA" , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 4 actual current sink value in uA") +PAR_ITEM_F32 (ePAR_CH4_VOL , 361 , "Ch4 Act Vout" , 0.0f , 10.0f , 0.0f , "V" , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 4 actual output voltage value in V") +PAR_ITEM_U16 (ePAR_CH4_CUR_RAW , 362 , "Ch4 Raw Isink" , 0U , 8191U , 0U , NULL , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 4 raw ADC current value") +PAR_ITEM_U16 (ePAR_CH4_VOL_RAW , 363 , "Ch4 Raw Vout" , 0U , 8191U , 0U , NULL , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 4 raw ADC voltage value") +PAR_ITEM_F32 (ePAR_CH4_AFE_VCC , 364 , "Ch4 AFE Vcc" , 0.0f , 10.0f , 0.0f , "V" , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 4 measured AFE Vcc voltage value in V") +PAR_ITEM_F32 (ePAR_CH4_AFE_RES , 366 , "Ch4 AFE Res" , 0.0f , 5.0e3f , 0.0f , "ohm" , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Channel 4 measured AFE resistance (pull-up + series) value in ohm") + + +/* ============================================================================================================================= */ +/* SYSTEM */ +/* ============================================================================================================================= */ + +/* System status */ +PAR_ITEM_U32 (ePAR_SYS_STATUS , 10000, "System status" , 0U , UINT32_MAX, 0U , NULL , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "0-None | For other look at sys_err.h") +PAR_ITEM_F32 (ePAR_SYS_CPU_LOAD , 10010, "CPU load" , 0.0f , 100.0f , 0.0f , "%" , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Current CPU load in %") +PAR_ITEM_F32 (ePAR_SYS_CPU_LOAD_MAX, 10018, "CPU Max. load" , 0.0f , 100.0f , 0.0f , "%" , ePAR_ACCESS_RO, ePAR_ROLE_ALL, ePAR_ROLE_NONE, false, "Maximum CPU load in %")