diff --git a/CMakeLists.txt b/CMakeLists.txt index be1741e..36cb13d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,12 +17,7 @@ endif() # Include directories include_directories(include) -# Core Library -set(IOLINKI_SOURCES - src/iolink_core.c -) add_library(iolinki STATIC - src/iolink_core.c src/phy_generic.c src/phy_virtual.c src/crc.c @@ -34,6 +29,7 @@ add_library(iolinki STATIC src/params.c src/data_storage.c src/device_info.c + src/device.c src/platform_stubs.c ) diff --git a/Doxyfile b/Doxyfile index a25e1c6..4eba72d 100644 --- a/Doxyfile +++ b/Doxyfile @@ -3,7 +3,7 @@ PROJECT_NAME = "iolinki" PROJECT_NUMBER = "1.1.2" PROJECT_BRIEF = "Open-Source IO-Link Device Stack" OUTPUT_DIRECTORY = docs -INPUT = include/iolinki src/iolink_core.c src/dll.c src/isdu.c src/events.c src/params.c src/data_storage.c src/device_info.c src/phy_virtual.c src/crc.c +INPUT = include/iolinki src/device.c src/dll.c src/isdu.c src/events.c src/params.c src/data_storage.c src/device_info.c src/phy_virtual.c src/crc.c RECURSIVE = NO OPTIMIZE_OUTPUT_FOR_C = YES EXTRACT_ALL = YES @@ -14,13 +14,11 @@ GENERATE_MAN = NO GENERATE_RTF = NO # Graphviz Support -HAVE_DOT = YES -DOT_PATH = /usr/bin -CLASS_DIAGRAMS = NO -COLLABORATION_GRAPH = YES -INCLUDE_GRAPH = YES -INCLUDED_BY_GRAPH = YES -GRAPHICAL_HIERARCHY = YES +HAVE_DOT = NO +COLLABORATION_GRAPH = NO +INCLUDE_GRAPH = NO +INCLUDED_BY_GRAPH = NO +GRAPHICAL_HIERARCHY = NO # Warning settings WARN_IF_UNDOCUMENTED = YES diff --git a/examples/bare_metal_app/src/main.c b/examples/bare_metal_app/src/main.c index 4d3be46..0f175c3 100644 --- a/examples/bare_metal_app/src/main.c +++ b/examples/bare_metal_app/src/main.c @@ -6,7 +6,7 @@ * See LICENSE for details. */ -#include "iolinki/iolink.h" +#include "iolinki/device.h" #include "iolinki/phy_virtual.h" /* Extern the volatile tick counter from time_utils_baremetal.c */ @@ -22,15 +22,17 @@ int main(void) /* Initialize stack with virtual PHY (simplified for bare metal, typically uses UART/SPI) */ iolink_phy_virtual_set_port("/dev/null"); const iolink_phy_api_t* phy = iolink_phy_virtual_get(); + iolink_device_ctx_t ctx; + iolink_device_config_t dev_config = {.phy = *phy}; - if (iolink_init(phy, NULL) != 0) { + if (iolink_device_init(&ctx, &dev_config) != 0) { return -1; } /* Main Super-Loop */ while (1) { /* Process Stack */ - iolink_process(); + iolink_device_process(&ctx); /* Simulate System Tick (Time Passage) */ sys_tick_handler(); diff --git a/examples/freertos_app/main.c b/examples/freertos_app/main.c index 9b4579e..8e12ace 100644 --- a/examples/freertos_app/main.c +++ b/examples/freertos_app/main.c @@ -20,7 +20,7 @@ #define pdMS_TO_TICKS(x) ((x) / 10) #endif -#include "iolinki/iolink.h" +#include "iolinki/device.h" #include "iolinki/platform.h" #include "iolinki/phy.h" @@ -43,19 +43,27 @@ const iolink_phy_api_t g_phy_freertos = { .recv_byte = NULL // Implement actual UART recv }; +static iolink_device_ctx_t g_device_ctx; +static iolink_device_config_t g_device_config = { + .phy = g_phy_freertos, +}; + /* IO-Link Stack Task */ void iolink_task_entry(void* pvParameters) { (void) pvParameters; /* Initialize Stack */ - iolink_init(&g_phy_freertos); + if (iolink_device_init(&g_device_ctx, &g_device_config) != 0) { + printf("IO-Link init failed\n"); + return; + } printf("IO-Link Task Started\n"); for (;;) { /* Process Stack */ - iolink_process(); + iolink_device_process(&g_device_ctx); /* Yield / Sleep to allow other tasks (1ms cycle) */ vTaskDelay(pdMS_TO_TICKS(1)); @@ -69,7 +77,8 @@ void app_task_entry(void* pvParameters) for (;;) { /* Trigger an event every 5 seconds safely */ - iolink_event_trigger(NULL /* ctx */, 0x1800, IOLINK_EVENT_TYPE_NOTIFICATION); + iolink_event_trigger(iolink_device_get_events_ctx(&g_device_ctx), 0x1800, + IOLINK_EVENT_TYPE_NOTIFICATION); vTaskDelay(pdMS_TO_TICKS(5000)); } diff --git a/examples/host_demo/src/main.c b/examples/host_demo/src/main.c index 8681150..36753d3 100644 --- a/examples/host_demo/src/main.c +++ b/examples/host_demo/src/main.c @@ -9,7 +9,7 @@ #include #include #include -#include "iolinki/iolink.h" +#include "iolinki/device.h" #include "iolinki/phy_virtual.h" int main(int argc, char* argv[]) @@ -68,8 +68,10 @@ int main(int argc, char* argv[]) /* Initialize stack with virtual PHY and config */ const iolink_phy_api_t* phy = iolink_phy_virtual_get(); + iolink_device_ctx_t ctx; + iolink_device_config_t dev_config = {.phy = *phy, .stack = config}; - if (iolink_init(phy, &config) != 0) { + if (iolink_device_init(&ctx, &dev_config) != 0) { printf("ERROR: Failed to initialize IO-Link stack\n"); return -1; } @@ -80,22 +82,22 @@ int main(int argc, char* argv[]) /* Simulate periodic processing and PD exchange */ while (1) { - iolink_process(); + iolink_device_process(&ctx); // ... /* Update input data (Device -> Master) */ - iolink_process(); + iolink_device_process(&ctx); /* Check for output data (Master -> Device) */ uint8_t pd_buffer[32]; - int len = iolink_pd_output_read(pd_buffer, sizeof(pd_buffer)); + int len = iolink_device_pd_output_read(&ctx, pd_buffer, sizeof(pd_buffer)); if (len > 0) { /* Echo + 1 */ for (int i = 0; i < len; i++) { pd_buffer[i]++; } - iolink_pd_input_update(pd_buffer, (size_t) len, true); + iolink_device_pd_input_update(&ctx, pd_buffer, (size_t) len, true); } /* 1ms cycle time simulation */ diff --git a/examples/simple_device/src/main.c b/examples/simple_device/src/main.c index ebea8a9..fe645fd 100644 --- a/examples/simple_device/src/main.c +++ b/examples/simple_device/src/main.c @@ -7,7 +7,7 @@ */ #include -#include "iolinki/iolink.h" +#include "iolinki/device.h" /** * @brief Placeholder for a real hardware PHY implementation @@ -23,7 +23,10 @@ int main(void) printf("IO-Link Simple Device Example\n"); /* Use default configuration */ - if (iolink_init(&g_hw_phy, NULL) != 0) { + iolink_device_ctx_t ctx; + iolink_device_config_t dev_config = {.phy = g_hw_phy}; + + if (iolink_device_init(&ctx, &dev_config) != 0) { printf("Failed to initialize IO-Link stack\n"); return -1; } diff --git a/examples/zephyr_app/src/main.c b/examples/zephyr_app/src/main.c index af35c44..ff5013e 100644 --- a/examples/zephyr_app/src/main.c +++ b/examples/zephyr_app/src/main.c @@ -14,7 +14,7 @@ #include #include -#include "iolinki/iolink.h" +#include "iolinki/device.h" #ifdef CONFIG_IOLINK_PHY_UART #include "platform/zephyr/phy_uart.h" @@ -31,9 +31,9 @@ int main(void) { LOG_INF("Starting IO-Link Zephyr Demo"); +#ifdef CONFIG_IOLINK_PHY_VIRTUAL const char *port = NULL; -#ifdef CONFIG_IOLINK_PHY_VIRTUAL port = getenv("IOLINK_PORT"); if (port) { iolink_phy_virtual_set_port(port); @@ -95,17 +95,20 @@ int main(void) /* Send Master Command: Read Direct Parameter 1 (Index 0) */ /* MC = 0x80, CKT = 0x00, CK = 0x24 */ uint8_t frame[] = {0x80, 0x24}; - phy_api->send(frame, 2); + phy_api->send(phy_api->user, frame, 2); uint8_t rx; - while (phy_api->recv_byte(&rx) > 0) { + while (phy_api->recv_byte(phy_api->user, &rx) > 0) { LOG_INF("Master RX: 0x%02X", rx); } k_msleep(2000); } #else - if (iolink_init(phy_api, &config) != 0) { + iolink_device_ctx_t ctx; + iolink_device_config_t dev_config = {.phy = *phy_api, .stack = config}; + + if (iolink_device_init(&ctx, &dev_config) != 0) { LOG_ERR("Failed to init IO-Link"); return -1; } @@ -114,7 +117,7 @@ int main(void) uint32_t last_update = 0; while (1) { - iolink_process(); + iolink_device_process(&ctx); /* Simulating sensor data change every 2000ms */ uint32_t now = k_uptime_get_32(); @@ -122,8 +125,8 @@ int main(void) last_update = now; sensor_val++; - uint8_t pd[2] = {sensor_val, 0xA5}; - iolink_pd_input_update(pd, 2, true); + const uint8_t pd[2] = {sensor_val, 0xA5}; + iolink_device_pd_input_update(&ctx, pd, 2, true); LOG_INF("Device PD Update: 0x%02X", sensor_val); } diff --git a/include/iolinki/application.h b/include/iolinki/application.h index 4b3c524..5f7dac3 100644 --- a/include/iolinki/application.h +++ b/include/iolinki/application.h @@ -18,34 +18,14 @@ * @brief IO-Link Application Layer API for Process Data */ -/** - * @brief Update Process Data Input (Device -> Master) - * - * @param data Pointer to input data - * @param len Length in bytes - * @param valid Data validity flag (true = valid, false = invalid) - * @return int 0 on success, negative on error - */ -int iolink_pd_input_update(const uint8_t* data, size_t len, bool valid); - -/** - * @brief Read Process Data Output (Master -> Device) - * - * @param data Pointer to buffer to store output data - * @param len Max length to read - * @return int Number of bytes read, negative on error - */ -int iolink_pd_output_read(uint8_t* data, size_t len); - /** * @brief Application lifecycle and Process Data callbacks. * * Any field may be NULL. Lifecycle callbacks fire once when the DLL enters the - * corresponding state (driven from iolink_process()). The PD callbacks fire on + * corresponding state. The PD callbacks fire on * every processed OPERATE cycle: @ref on_pd_output reports the output Process * Data received from the Master (Master -> Device), and @ref on_pd_input reports * the input Process Data the Device is currently publishing (Device -> Master). - * They complement the polling API (iolink_pd_output_read / iolink_pd_input_update). */ typedef struct { @@ -56,14 +36,4 @@ typedef struct void (*on_pd_output)(uint8_t* data, uint8_t len); /**< Output PD received */ } iolink_app_callbacks_t; -/** - * @brief Register application callbacks (or NULL to clear). - * - * The structure is referenced, not copied, so it must remain valid for the - * lifetime of the stack. - * - * @param callbacks Pointer to a callbacks structure, or NULL. - */ -void iolink_app_register(const iolink_app_callbacks_t* callbacks); - #endif // IOLINK_APPLICATION_H diff --git a/include/iolinki/data_storage.h b/include/iolinki/data_storage.h index 3ea8221..c17af6b 100644 --- a/include/iolinki/data_storage.h +++ b/include/iolinki/data_storage.h @@ -10,6 +10,7 @@ #define IOLINK_DATA_STORAGE_H #include "iolinki/protocol.h" +#include "iolinki/params.h" #include #include #include @@ -91,6 +92,7 @@ typedef struct { iolink_ds_state_t state; /**< Current DS state machine position */ const iolink_ds_storage_api_t* storage; /**< Bound storage implementation API */ + iolink_params_ctx_t* params_ctx; /**< Device-local writable parameter context */ uint16_t current_checksum; /**< Last calculated local parameter checksum */ uint16_t master_checksum; /**< Most recent checksum verified by Master */ uint8_t image[IOLINK_DS_IMAGE_MAX]; /**< Serialized parameter image (DS_Data 0x0003) */ @@ -104,6 +106,7 @@ typedef struct * @param storage Optional storage implementation hooks (can be NULL for RAM-only) */ void iolink_ds_init(iolink_ds_ctx_t* ctx, const iolink_ds_storage_api_t* storage); +void iolink_ds_bind_params(iolink_ds_ctx_t* ctx, iolink_params_ctx_t* params_ctx); /** * @brief Calculate a standard 16-bit checksum for a parameter block diff --git a/include/iolinki/device.h b/include/iolinki/device.h new file mode 100644 index 0000000..b561149 --- /dev/null +++ b/include/iolinki/device.h @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2026 Andrii Shylenko + * SPDX-License-Identifier: GPL-3.0-or-later + * + * This file is part of iolinki. + * See LICENSE for details. + */ + +#ifndef IOLINK_DEVICE_H +#define IOLINK_DEVICE_H + +#include "iolinki/application.h" +#include "iolinki/data_storage.h" +#include "iolinki/device_info.h" +#include "iolinki/dll.h" +#include "iolinki/iolink.h" +#include "iolinki/params.h" +#include "iolinki/phy.h" +#include +#include +#include + +typedef uint64_t (*iolink_device_time_us_fn)(void* user); +typedef void (*iolink_device_lock_fn)(void* user); +typedef void (*iolink_device_unlock_fn)(void* user); + +typedef iolink_phy_api_t iolink_device_phy_t; + +typedef struct +{ + iolink_device_phy_t phy; + iolink_config_t stack; + const iolink_app_callbacks_t* app_callbacks; + const iolink_device_info_t* device_info; + const iolink_ds_storage_api_t* ds_storage; + iolink_device_time_us_fn time_us; + iolink_device_lock_fn lock; + iolink_device_unlock_fn unlock; + void* user; +} iolink_device_config_t; + +typedef struct +{ + /* Private fields. Allocate this object directly, but use API functions. */ + iolink_dll_ctx_t dll; + iolink_config_t stack_config; + iolink_device_info_ctx_t device_info; + iolink_params_ctx_t params; + const iolink_device_config_t* config; + iolink_reset_handler_t reset_handler; + uint8_t direct_param_page2[16]; +} iolink_device_ctx_t; + +size_t iolink_device_ctx_size(void); +int iolink_device_init(iolink_device_ctx_t* ctx, const iolink_device_config_t* config); +void iolink_device_process(iolink_device_ctx_t* ctx); +int iolink_device_pd_input_update(iolink_device_ctx_t* ctx, const uint8_t* data, size_t len, + bool valid); +int iolink_device_pd_output_read(iolink_device_ctx_t* ctx, uint8_t* data, size_t len); +void iolink_device_set_reset_handler(iolink_device_ctx_t* ctx, iolink_reset_handler_t handler); +iolink_events_ctx_t* iolink_device_get_events_ctx(iolink_device_ctx_t* ctx); +iolink_ds_ctx_t* iolink_device_get_ds_ctx(iolink_device_ctx_t* ctx); +iolink_dll_state_t iolink_device_get_state(const iolink_device_ctx_t* ctx); +iolink_phy_mode_t iolink_device_get_phy_mode(const iolink_device_ctx_t* ctx); +iolink_baudrate_t iolink_device_get_baudrate(const iolink_device_ctx_t* ctx); +void iolink_device_get_dll_stats(const iolink_device_ctx_t* ctx, iolink_dll_stats_t* out_stats); +void iolink_device_set_timing_enforcement(iolink_device_ctx_t* ctx, bool enable); +void iolink_device_set_t_ren_limit_us(iolink_device_ctx_t* ctx, uint32_t limit_us); +iolink_m_seq_type_t iolink_device_get_m_seq_type(const iolink_device_ctx_t* ctx); +uint8_t iolink_device_get_pd_in_len(const iolink_device_ctx_t* ctx); +uint8_t iolink_device_get_pd_out_len(const iolink_device_ctx_t* ctx); +int iolink_device_set_pd_length(iolink_device_ctx_t* ctx, uint8_t pd_in_len, uint8_t pd_out_len); + +#endif // IOLINK_DEVICE_H diff --git a/include/iolinki/device_info.h b/include/iolinki/device_info.h index 434d37e..99177b6 100644 --- a/include/iolinki/device_info.h +++ b/include/iolinki/device_info.h @@ -51,6 +51,22 @@ typedef struct uint16_t access_locks; /* Index 0x000C - Device Access Locks */ } iolink_device_info_t; +typedef struct +{ + const iolink_device_info_t* configured; + iolink_device_info_t defaults; + char application_tag[33]; + uint16_t access_locks; +} iolink_device_info_ctx_t; + +void iolink_device_info_ctx_init(iolink_device_info_ctx_t* ctx, + const iolink_device_info_t* configured); +const iolink_device_info_t* iolink_device_info_ctx_get(const iolink_device_info_ctx_t* ctx); +int iolink_device_info_ctx_set_application_tag(iolink_device_info_ctx_t* ctx, const char* tag, + uint8_t len); +uint16_t iolink_device_info_ctx_get_access_locks(const iolink_device_info_ctx_t* ctx); +void iolink_device_info_ctx_set_access_locks(iolink_device_info_ctx_t* ctx, uint16_t locks); + /** * @brief Initialize device information * @param info Pointer to device info structure diff --git a/include/iolinki/dll.h b/include/iolinki/dll.h index 046a3c4..647efc0 100644 --- a/include/iolinki/dll.h +++ b/include/iolinki/dll.h @@ -39,7 +39,7 @@ typedef enum * transient state is missed. Used by the core to drive application lifecycle * callbacks. */ -typedef void (*iolink_dll_state_cb_t)(iolink_dll_state_t state); +typedef void (*iolink_dll_state_cb_t)(void* user, iolink_dll_state_t state); #include "iolinki/events.h" #include "iolinki/isdu.h" @@ -124,6 +124,7 @@ typedef struct iolink_ds_ctx_t ds; /**< Data Storage engine */ iolink_dll_state_cb_t state_cb; /**< Optional state-change hook (set by core) */ + void* state_cb_user; /**< User pointer passed to state_cb */ } iolink_dll_ctx_t; /** diff --git a/include/iolinki/iolink.h b/include/iolinki/iolink.h index a45aca2..218ab89 100644 --- a/include/iolinki/iolink.h +++ b/include/iolinki/iolink.h @@ -10,14 +10,10 @@ #define IOLINK_H #include -#include -#include "iolinki/phy.h" -#include "iolinki/application.h" -#include "iolinki/dll.h" /** * @file iolink.h - * @brief Main header for iolinki IO-Link stack + * @brief Shared IO-Link stack types */ /** @@ -51,25 +47,6 @@ typedef struct uint32_t t_pd_us; /**< Power-on delay (t_pd) in microseconds */ } iolink_config_t; -/** - * @brief Initialize the IO-Link stack - * - * Configures the internal state machine, ISDU engine, and PHY interface. - * - * @param phy Pointer to the PHY implementation API - * @param config Pointer to stack configuration (copied internally) - * @return int 0 on success, negative error code (e.g. -1 for NULL PHY) - */ -int iolink_init(const iolink_phy_api_t* phy, const iolink_config_t* config); - -/** - * @brief Process the IO-Link stack logic - * - * Main execution entry point. Handles PHY byte collection, state transitions, - * frame assembly, and response generation. Must be called in the main loop. - */ -void iolink_process(void); - /** * @brief Reset request type delivered to the application reset handler. */ @@ -86,121 +63,4 @@ typedef enum */ typedef void (*iolink_reset_handler_t)(iolink_reset_type_t type); -/** - * @brief Register an optional handler for Master-issued reset System Commands. - * - * The stack records reset requests (0x80/0x81) during ISDU processing and - * dispatches them to @p handler from within iolink_process(), then clears the - * pending flag. If no handler is registered the request is acknowledged and - * discarded (the stack cannot reboot the host on its own). - * - * @param handler Callback, or NULL to clear. - */ -void iolink_set_reset_handler(iolink_reset_handler_t handler); - -#include "iolinki/events.h" -#include "iolinki/data_storage.h" - -/** - * @brief Get the events context of the stack - * - * Used to trigger or check for pending diagnostic events. - * - * @return iolink_events_ctx_t* Pointer to the internal events context - */ -iolink_events_ctx_t* iolink_get_events_ctx(void); - -/** - * @brief Get the data storage context of the stack - * - * Used to manage Data Storage (DS) upload/download and synchronization. - * - * @return iolink_ds_ctx_t* Pointer to the internal DS context - */ -iolink_ds_ctx_t* iolink_get_ds_ctx(void); - -/** - * @brief Get current DLL state - * - * @return iolink_dll_state_t Current state - */ -iolink_dll_state_t iolink_get_state(void); - -/** - * @brief Get current PHY mode - * - * @return iolink_phy_mode_t Current mode - */ -iolink_phy_mode_t iolink_get_phy_mode(void); - -/** - * @brief Get current baudrate - * - * @return iolink_baudrate_t Current baudrate - */ -iolink_baudrate_t iolink_get_baudrate(void); - -/** - * @brief Get DLL statistics snapshot - * - * @param out_stats Output stats structure - */ -void iolink_get_dll_stats(iolink_dll_stats_t* out_stats); - -/** - * @brief Enable/disable timing enforcement (t_ren / t_cycle) - * - * @param enable true to enable, false to disable - */ -void iolink_set_timing_enforcement(bool enable); - -/** - * @brief Override t_ren limit (applies to all baudrates) - * - * @param limit_us New t_ren limit in microseconds (0 disables enforcement) - */ -void iolink_set_t_ren_limit_us(uint32_t limit_us); - -/** - * @brief Get configured M-sequence type - * - * @return iolink_m_seq_type_t Current M-sequence type - */ -iolink_m_seq_type_t iolink_get_m_seq_type(void); - -/** - * @brief Get the current PD In length - * - * Returns the runtime length (equal to the configured value for fixed - * M-sequence types; tracks iolink_set_pd_length() for variable types). - * - * @return uint8_t PD In length in bytes - */ -uint8_t iolink_get_pd_in_len(void); - -/** - * @brief Get the current PD Out length - * - * Returns the runtime length (equal to the configured value for fixed - * M-sequence types; tracks iolink_set_pd_length() for variable types). - * - * @return uint8_t PD Out length in bytes - */ -uint8_t iolink_get_pd_out_len(void); - -/** - * @brief Set the runtime Process Data lengths for variable-length M-sequences. - * - * Only valid for variable PD M-sequence types (TYPE_1_V / TYPE_2_V). The new - * lengths take effect on the next cyclic exchange and are reflected in the PD - * descriptors (index 0x001D and Direct Parameter page 1), so a Master can - * re-read them. Lengths are clamped to the maximum configured at init. - * - * @param pd_in_len New input PD length in bytes (Device -> Master) - * @param pd_out_len New output PD length in bytes (Master -> Device) - * @return int 0 on success, -1 if a length exceeds the configured maximum, - * -2 if the configured M-sequence type is not variable. - */ -int iolink_set_pd_length(uint8_t pd_in_len, uint8_t pd_out_len); - #endif // IOLINK_H diff --git a/include/iolinki/isdu.h b/include/iolinki/isdu.h index 7e5aaa5..d00f7b4 100644 --- a/include/iolinki/isdu.h +++ b/include/iolinki/isdu.h @@ -13,6 +13,7 @@ #include #include #include "iolinki/config.h" +#include "iolinki/params.h" /** * @file isdu.h @@ -82,9 +83,11 @@ typedef struct uint8_t error_code; /**< IO-Link ISDU Error Code (0x80XX) */ /* Pointers to external dependencies */ - void* event_ctx; /**< Diagnostic host backlink */ - void* ds_ctx; /**< Data Storage context for system commands */ - void* dll_ctx; /**< DLL context for statistics access */ + void* event_ctx; /**< Diagnostic host backlink */ + void* ds_ctx; /**< Data Storage context for system commands */ + void* dll_ctx; /**< DLL context for statistics access */ + iolink_params_ctx_t* params_ctx; /**< Device-local writable parameter context */ + void* direct_param_page2; /**< Per-device Direct Parameter page 2 storage */ /* System Command Flags */ bool reset_pending; /**< Device reset requested (0x80) */ diff --git a/include/iolinki/params.h b/include/iolinki/params.h index fee0c78..46abd33 100644 --- a/include/iolinki/params.h +++ b/include/iolinki/params.h @@ -12,17 +12,38 @@ #include #include #include +#include "iolinki/device_info.h" /** * @file params.h * @brief IO-Link Parametrization Manager */ +/** + * Per-device parameter manager context. + */ +typedef struct +{ + char application_tag[33]; + char function_tag[33]; + char location_tag[33]; + bool application_tag_valid; + bool function_tag_valid; + bool location_tag_valid; + iolink_device_info_ctx_t* device_info; +} iolink_params_ctx_t; + +void iolink_params_ctx_init(iolink_params_ctx_t* ctx, iolink_device_info_ctx_t* device_info); +int iolink_params_ctx_get(const iolink_params_ctx_t* ctx, uint16_t index, uint8_t subindex, + uint8_t* buffer, size_t max_len); +int iolink_params_ctx_set(iolink_params_ctx_t* ctx, uint16_t index, uint8_t subindex, + const uint8_t* data, size_t len, bool persist); +void iolink_params_ctx_factory_reset(iolink_params_ctx_t* ctx); + /** * @brief Initialize the parameter manager * - * Sets up internal lookup tables and attempts to load persistent - * configuration from Non-Volatile Memory (NVM). + * Compatibility wrapper around the context API. */ void iolink_params_init(void); diff --git a/include/iolinki/phy.h b/include/iolinki/phy.h index a58e77e..81a570b 100644 --- a/include/iolinki/phy.h +++ b/include/iolinki/phy.h @@ -46,23 +46,25 @@ typedef enum */ typedef struct { + void* user; + /** * @brief Initialize transceiver hardware * @return 0 on success, negative error code on hardware failure */ - int (*init)(void); + int (*init)(void* user); /** * @brief Set PHY operating mode (SDCI vs SIO) * @param mode Target mode */ - void (*set_mode)(iolink_phy_mode_t mode); + void (*set_mode)(void* user, iolink_phy_mode_t mode); /** * @brief Set communication baudrate * @param baudrate Target COMx speed */ - void (*set_baudrate)(iolink_baudrate_t baudrate); + void (*set_baudrate)(void* user, iolink_baudrate_t baudrate); /** * @brief Send a buffer of data over the line @@ -70,14 +72,14 @@ typedef struct * @param len Number of bytes to transmit * @return Number of bytes actually sent, or negative on error */ - int (*send)(const uint8_t* data, size_t len); + int (*send)(void* user, const uint8_t* data, size_t len); /** * @brief Non-blocking receive for a single byte * @param byte Pointer to store received byte * @return 1 if byte available and read, 0 if nothing received, negative on error */ - int (*recv_byte)(uint8_t* byte); + int (*recv_byte)(void* user, uint8_t* byte); /* Optional Diagnostic/Support Functions (can be NULL) */ @@ -85,25 +87,25 @@ typedef struct * @brief Detect wake-up pulse (80µs pulse on C/Q line) * @return 1 if wake-up pulse detected during current window, 0 otherwise */ - int (*detect_wakeup)(void); + int (*detect_wakeup)(void* user); /** * @brief Manually set C/Q line state (for SIO push-pull) * @param state 0 for Low, 1 for High */ - void (*set_cq_line)(uint8_t state); + void (*set_cq_line)(void* user, uint8_t state); /** * @brief Get L+ supply voltage * @return Voltage in millivolts, negative if measurement unavailable */ - int (*get_voltage_mv)(void); + int (*get_voltage_mv)(void* user); /** * @brief Check for hardware fault condition * @return true if short circuit or overtemperature detected */ - bool (*is_short_circuit)(void); + bool (*is_short_circuit)(void* user); } iolink_phy_api_t; #endif // IOLINK_PHY_H diff --git a/samples/iolink_device/src/main.c b/samples/iolink_device/src/main.c index 2d218a5..404bbe3 100644 --- a/samples/iolink_device/src/main.c +++ b/samples/iolink_device/src/main.c @@ -12,14 +12,14 @@ * - initializes the stack with a small fixed-PD configuration, * - registers application lifecycle and process-data callbacks, * - publishes a 2-byte input process-data word every cycle, - * - services the stack from the main loop via iolink_process(). + * - services the stack from the main loop via iolink_device_process(). */ #include #include #include -#include "iolinki/iolink.h" +#include "iolinki/device.h" #ifdef CONFIG_IOLINK_PHY_UART #include "platform/zephyr/phy_uart.h" @@ -76,18 +76,23 @@ int main(void) config.pd_out_len = SAMPLE_PD_OUT_LEN; config.t_pd_us = 0; - if (iolink_init(phy, &config) != 0) { - LOG_ERR("iolink_init failed"); + iolink_device_ctx_t ctx; + iolink_device_config_t dev_config = { + .phy = *phy, + .stack = config, + .app_callbacks = &app_callbacks, + }; + + if (iolink_device_init(&ctx, &dev_config) != 0) { + LOG_ERR("iolink_device_init failed"); return -1; } - iolink_app_register(&app_callbacks); - uint8_t counter = 0; uint32_t last_update = 0; while (1) { - iolink_process(); + iolink_device_process(&ctx); uint32_t now = k_uptime_get_32(); if (now - last_update >= 1000) { @@ -95,7 +100,7 @@ int main(void) counter++; const uint8_t pd_in[SAMPLE_PD_IN_LEN] = {counter, 0xA5}; - iolink_pd_input_update(pd_in, sizeof(pd_in), true); + iolink_device_pd_input_update(&ctx, pd_in, sizeof(pd_in), true); LOG_INF("Published PD in: 0x%02X 0x%02X", pd_in[0], pd_in[1]); } diff --git a/src/data_storage.c b/src/data_storage.c index 9c2bff4..f05dd9b 100644 --- a/src/data_storage.c +++ b/src/data_storage.c @@ -46,6 +46,31 @@ void iolink_ds_init(iolink_ds_ctx_t* ctx, const iolink_ds_storage_api_t* storage ctx->state = IOLINK_DS_STATE_IDLE; } +void iolink_ds_bind_params(iolink_ds_ctx_t* ctx, iolink_params_ctx_t* params_ctx) +{ + if (ctx != NULL) { + ctx->params_ctx = params_ctx; + } +} + +static int ds_params_get(const iolink_ds_ctx_t* ctx, uint16_t index, uint8_t subindex, + uint8_t* buffer, size_t max_len) +{ + if ((ctx != NULL) && (ctx->params_ctx != NULL)) { + return iolink_params_ctx_get(ctx->params_ctx, index, subindex, buffer, max_len); + } + return iolink_params_get(index, subindex, buffer, max_len); +} + +static int ds_params_set(iolink_ds_ctx_t* ctx, uint16_t index, uint8_t subindex, + const uint8_t* data, size_t len, bool persist) +{ + if ((ctx != NULL) && (ctx->params_ctx != NULL)) { + return iolink_params_ctx_set(ctx->params_ctx, index, subindex, data, len, persist); + } + return iolink_params_set(index, subindex, data, len, persist); +} + uint16_t iolink_ds_calc_checksum(const uint8_t* data, size_t len) { /* Fletcher-16 over the serialized image. The Master treats the DS blob and @@ -73,7 +98,7 @@ int iolink_ds_build_image(iolink_ds_ctx_t* ctx) for (size_t i = 0U; i < DS_PARAM_COUNT; ++i) { uint8_t value[DS_PARAM_VALUE_MAX]; int n = - iolink_params_get(k_ds_params[i].index, k_ds_params[i].subindex, value, sizeof(value)); + ds_params_get(ctx, k_ds_params[i].index, k_ds_params[i].subindex, value, sizeof(value)); if (n < 0) { n = 0; /* Parameter not present -> empty record */ } @@ -124,7 +149,7 @@ int iolink_ds_apply_image(iolink_ds_ctx_t* ctx, const uint8_t* data, size_t len) uint16_t index = (uint16_t) (((uint16_t) data[pos] << 8) | (uint16_t) data[pos + 1U]); uint8_t subindex = data[pos + 2U]; size_t rlen = (size_t) data[pos + 3U]; - (void) iolink_params_set(index, subindex, &data[pos + DS_RECORD_HEADER], rlen, true); + (void) ds_params_set(ctx, index, subindex, &data[pos + DS_RECORD_HEADER], rlen, true); pos += DS_RECORD_HEADER + rlen; } diff --git a/src/device.c b/src/device.c new file mode 100644 index 0000000..6b23ad3 --- /dev/null +++ b/src/device.c @@ -0,0 +1,298 @@ +/* + * Copyright (C) 2026 Andrii Shylenko + * SPDX-License-Identifier: GPL-3.0-or-later + * + * This file is part of iolinki. + * See LICENSE for details. + */ + +#include "iolinki/device.h" +#include "iolinki/platform.h" +#include "iolinki/time_utils.h" +#include + +static void device_state_cb(void* user, iolink_dll_state_t state) +{ + iolink_device_ctx_t* ctx = (iolink_device_ctx_t*) user; + + if ((ctx == NULL) || (ctx->config == NULL) || (ctx->config->app_callbacks == NULL)) { + return; + } + + switch (state) { + case IOLINK_DLL_STATE_STARTUP: + if (ctx->config->app_callbacks->on_startup != NULL) { + ctx->config->app_callbacks->on_startup(); + } + break; + case IOLINK_DLL_STATE_PREOPERATE: + if (ctx->config->app_callbacks->on_preoperate != NULL) { + ctx->config->app_callbacks->on_preoperate(); + } + break; + case IOLINK_DLL_STATE_OPERATE: + if (ctx->config->app_callbacks->on_operate != NULL) { + ctx->config->app_callbacks->on_operate(); + } + break; + default: + break; + } +} + +static void device_apply_stack_config(iolink_device_ctx_t* ctx) +{ + ctx->dll.m_seq_type = (uint8_t) ctx->stack_config.m_seq_type; + ctx->dll.pd_in_len = ctx->stack_config.pd_in_len; + ctx->dll.pd_out_len = ctx->stack_config.pd_out_len; + ctx->dll.min_cycle_time_us = (uint32_t) ctx->stack_config.min_cycle_time * 100U; + ctx->dll.t_pd_delay_us = ctx->stack_config.t_pd_us; + if (ctx->dll.t_pd_delay_us > 0U) { + ctx->dll.t_pd_deadline_us = iolink_time_get_us() + (uint64_t) ctx->dll.t_pd_delay_us; + } + else { + ctx->dll.t_pd_deadline_us = 0U; + } + + if ((ctx->dll.m_seq_type == IOLINK_M_SEQ_TYPE_2_1) || + (ctx->dll.m_seq_type == IOLINK_M_SEQ_TYPE_2_2) || + (ctx->dll.m_seq_type == IOLINK_M_SEQ_TYPE_2_V)) { + ctx->dll.od_len = 2U; + } + else { + ctx->dll.od_len = 1U; + } + + ctx->dll.pd_in_len_current = ctx->dll.pd_in_len; + ctx->dll.pd_out_len_current = ctx->dll.pd_out_len; + ctx->dll.pd_in_len_max = ctx->dll.pd_in_len; + ctx->dll.pd_out_len_max = ctx->dll.pd_out_len; +} + +size_t iolink_device_ctx_size(void) +{ + return sizeof(iolink_device_ctx_t); +} + +int iolink_device_init(iolink_device_ctx_t* ctx, const iolink_device_config_t* config) +{ + if ((ctx == NULL) || (config == NULL)) { + return -1; + } + + (void) memset(ctx, 0, sizeof(*ctx)); + (void) memcpy(&ctx->stack_config, &config->stack, sizeof(ctx->stack_config)); + ctx->config = config; + + if (config->phy.init != NULL) { + int err = config->phy.init(config->phy.user); + if (err != 0) { + return err; + } + } + + iolink_device_info_ctx_init(&ctx->device_info, config->device_info); + iolink_params_ctx_init(&ctx->params, &ctx->device_info); + iolink_dll_init(&ctx->dll, &config->phy); + ctx->dll.isdu.direct_param_page2 = ctx->direct_param_page2; + ctx->dll.isdu.params_ctx = &ctx->params; + ctx->dll.state_cb = device_state_cb; + ctx->dll.state_cb_user = ctx; + if (config->ds_storage != NULL) { + iolink_ds_init(&ctx->dll.ds, config->ds_storage); + ctx->dll.isdu.ds_ctx = &ctx->dll.ds; + } + iolink_ds_bind_params(&ctx->dll.ds, &ctx->params); + device_apply_stack_config(ctx); + + if ((config->app_callbacks != NULL) && (config->app_callbacks->on_startup != NULL)) { + config->app_callbacks->on_startup(); + } + + return 0; +} + +void iolink_device_process(iolink_device_ctx_t* ctx) +{ + if (ctx == NULL) { + return; + } + + iolink_dll_process(&ctx->dll); + + if (ctx->dll.isdu.reset_pending) { + ctx->dll.isdu.reset_pending = false; + if (ctx->reset_handler != NULL) { + ctx->reset_handler(IOLINK_RESET_DEVICE); + } + } + if (ctx->dll.isdu.app_reset_pending) { + ctx->dll.isdu.app_reset_pending = false; + if (ctx->reset_handler != NULL) { + ctx->reset_handler(IOLINK_RESET_APPLICATION); + } + } + + if ((ctx->config != NULL) && (ctx->config->app_callbacks != NULL) && + (ctx->dll.state == IOLINK_DLL_STATE_OPERATE)) { + if (ctx->config->app_callbacks->on_pd_output != NULL) { + ctx->config->app_callbacks->on_pd_output(ctx->dll.pd_out, ctx->dll.pd_out_len_current); + } + if (ctx->config->app_callbacks->on_pd_input != NULL) { + ctx->config->app_callbacks->on_pd_input(ctx->dll.pd_in, ctx->dll.pd_in_len_current); + } + } +} + +int iolink_device_pd_input_update(iolink_device_ctx_t* ctx, const uint8_t* data, size_t len, + bool valid) +{ + if ((ctx == NULL) || (data == NULL) || (len > sizeof(ctx->dll.pd_in))) { + return -1; + } + + if ((ctx->config != NULL) && (ctx->config->lock != NULL)) { + ctx->config->lock(ctx->config->user); + } + else { + iolink_critical_enter(); + } + + (void) memcpy(ctx->dll.pd_in, data, len); + ctx->dll.pd_in_len = (uint8_t) len; + ctx->dll.pd_in_len_current = (uint8_t) len; + ctx->dll.pd_valid = valid; + ctx->dll.pd_in_toggle = !ctx->dll.pd_in_toggle; + + if ((ctx->config != NULL) && (ctx->config->unlock != NULL)) { + ctx->config->unlock(ctx->config->user); + } + else { + iolink_critical_exit(); + } + + return 0; +} + +int iolink_device_pd_output_read(iolink_device_ctx_t* ctx, uint8_t* data, size_t len) +{ + uint8_t read_len; + + if ((ctx == NULL) || (data == NULL)) { + return -1; + } + + if ((ctx->config != NULL) && (ctx->config->lock != NULL)) { + ctx->config->lock(ctx->config->user); + } + else { + iolink_critical_enter(); + } + + read_len = (len < ctx->dll.pd_out_len_current) ? (uint8_t) len : ctx->dll.pd_out_len_current; + (void) memcpy(data, ctx->dll.pd_out, read_len); + + if ((ctx->config != NULL) && (ctx->config->unlock != NULL)) { + ctx->config->unlock(ctx->config->user); + } + else { + iolink_critical_exit(); + } + + return (int) read_len; +} + +void iolink_device_set_reset_handler(iolink_device_ctx_t* ctx, iolink_reset_handler_t handler) +{ + if (ctx != NULL) { + ctx->reset_handler = handler; + } +} + +iolink_events_ctx_t* iolink_device_get_events_ctx(iolink_device_ctx_t* ctx) +{ + return (ctx != NULL) ? &ctx->dll.events : NULL; +} + +iolink_ds_ctx_t* iolink_device_get_ds_ctx(iolink_device_ctx_t* ctx) +{ + return (ctx != NULL) ? &ctx->dll.ds : NULL; +} + +iolink_dll_state_t iolink_device_get_state(const iolink_device_ctx_t* ctx) +{ + return (ctx != NULL) ? ctx->dll.state : IOLINK_DLL_STATE_STARTUP; +} + +iolink_phy_mode_t iolink_device_get_phy_mode(const iolink_device_ctx_t* ctx) +{ + return (ctx != NULL) ? iolink_dll_get_phy_mode(&ctx->dll) : IOLINK_PHY_MODE_SIO; +} + +iolink_baudrate_t iolink_device_get_baudrate(const iolink_device_ctx_t* ctx) +{ + return (ctx != NULL) ? iolink_dll_get_baudrate(&ctx->dll) : IOLINK_BAUDRATE_COM2; +} + +void iolink_device_get_dll_stats(const iolink_device_ctx_t* ctx, iolink_dll_stats_t* out_stats) +{ + if (ctx != NULL) { + iolink_dll_get_stats(&ctx->dll, out_stats); + } +} + +void iolink_device_set_timing_enforcement(iolink_device_ctx_t* ctx, bool enable) +{ + if (ctx != NULL) { + iolink_dll_set_timing_enforcement(&ctx->dll, enable); + } +} + +void iolink_device_set_t_ren_limit_us(iolink_device_ctx_t* ctx, uint32_t limit_us) +{ + if (ctx != NULL) { + iolink_dll_set_t_ren_limit_us(&ctx->dll, limit_us); + } +} + +iolink_m_seq_type_t iolink_device_get_m_seq_type(const iolink_device_ctx_t* ctx) +{ + return (ctx != NULL) ? (iolink_m_seq_type_t) ctx->dll.m_seq_type : IOLINK_M_SEQ_TYPE_0; +} + +uint8_t iolink_device_get_pd_in_len(const iolink_device_ctx_t* ctx) +{ + uint8_t pd_in_len = 0U; + + if (ctx != NULL) { + iolink_dll_get_pd_length(&ctx->dll, &pd_in_len, NULL); + } + + return pd_in_len; +} + +uint8_t iolink_device_get_pd_out_len(const iolink_device_ctx_t* ctx) +{ + uint8_t pd_out_len = 0U; + + if (ctx != NULL) { + iolink_dll_get_pd_length(&ctx->dll, NULL, &pd_out_len); + } + + return pd_out_len; +} + +int iolink_device_set_pd_length(iolink_device_ctx_t* ctx, uint8_t pd_in_len, uint8_t pd_out_len) +{ + if (ctx == NULL) { + return -1; + } + if ((ctx->dll.m_seq_type != IOLINK_M_SEQ_TYPE_1_V) && + (ctx->dll.m_seq_type != IOLINK_M_SEQ_TYPE_2_V)) { + return -2; + } + if ((pd_in_len > ctx->dll.pd_in_len_max) || (pd_out_len > ctx->dll.pd_out_len_max)) { + return -1; + } + return iolink_dll_set_pd_length(&ctx->dll, pd_in_len, pd_out_len); +} diff --git a/src/device_info.c b/src/device_info.c index 5228792..9602c07 100644 --- a/src/device_info.c +++ b/src/device_info.c @@ -7,12 +7,10 @@ */ #include "iolinki/device_info.h" +#include #include -static const iolink_device_info_t* g_device_info = NULL; - -/* Default device info (can be overridden by application) */ -static iolink_device_info_t g_default_info = { +static const iolink_device_info_t k_default_info = { .vendor_name = "iolinki", .vendor_text = "Open-Source IO-Link Stack", .product_name = "Generic IO-Link Device", @@ -36,65 +34,114 @@ static iolink_device_info_t g_default_info = { .access_locks = 0x0000U /* All unlocked by default */ }; -static char g_app_tag_buffer[33] = "DefaultTag"; +static iolink_device_info_ctx_t g_legacy_device_info_ctx; +static bool g_legacy_device_info_ctx_initialized = false; -void iolink_device_info_init(const iolink_device_info_t* info) +void iolink_device_info_ctx_init(iolink_device_info_ctx_t* ctx, + const iolink_device_info_t* configured) { - /* If user provides info, we use it (const). */ - /* Note: Writing to app tag when using user-provided const info will fail or require separate - * handling. */ - /* For now, we assume default info or shallow copy if needed. */ - if (info != NULL) { - /* Shallow copy to internal non-const struct to allow modification of pointers? */ - /* Or just update the global pointer. */ - g_device_info = info; + const iolink_device_info_t* source = configured; + + if (ctx == NULL) { + return; + } + + if (source == NULL) { + source = &k_default_info; + } + + ctx->configured = configured; + ctx->defaults = *source; + ctx->access_locks = source->access_locks; + + if (source->application_tag != NULL) { + size_t len = strlen(source->application_tag); + if (len > 32U) { + len = 32U; + } + (void) memcpy(ctx->application_tag, source->application_tag, len); + ctx->application_tag[len] = '\0'; } else { - g_device_info = &g_default_info; - g_default_info.application_tag = g_app_tag_buffer; + ctx->application_tag[0] = '\0'; } + + ctx->defaults.application_tag = ctx->application_tag; + ctx->defaults.access_locks = ctx->access_locks; } -int iolink_device_info_set_application_tag(const char* tag, uint8_t len) +const iolink_device_info_t* iolink_device_info_ctx_get(const iolink_device_info_ctx_t* ctx) { - if (tag == NULL) { + if (ctx == NULL) { + return NULL; + } + return &ctx->defaults; +} + +int iolink_device_info_ctx_set_application_tag(iolink_device_info_ctx_t* ctx, const char* tag, + uint8_t len) +{ + if ((ctx == NULL) || (tag == NULL)) { return -1; } - if (len >= sizeof(g_app_tag_buffer)) { + if (len >= sizeof(ctx->application_tag)) { return -1; } - /* If we are using g_default_info, we can update the buffer */ - if (g_device_info == &g_default_info) { - (void) memcpy(g_app_tag_buffer, tag, len); - g_app_tag_buffer[len] = '\0'; - g_default_info.application_tag = g_app_tag_buffer; - return 0; + (void) memcpy(ctx->application_tag, tag, len); + ctx->application_tag[len] = '\0'; + ctx->defaults.application_tag = ctx->application_tag; + return 0; +} + +uint16_t iolink_device_info_ctx_get_access_locks(const iolink_device_info_ctx_t* ctx) +{ + if (ctx == NULL) { + return 0U; } - return -1; /* Cannot update read-only user info */ + return ctx->access_locks; } -const iolink_device_info_t* iolink_device_info_get(void) +void iolink_device_info_ctx_set_access_locks(iolink_device_info_ctx_t* ctx, uint16_t locks) { - if (g_device_info == NULL) { - g_device_info = &g_default_info; + if (ctx == NULL) { + return; } - return g_device_info; + ctx->access_locks = locks; + ctx->defaults.access_locks = locks; } -uint16_t iolink_device_info_get_access_locks(void) +static iolink_device_info_ctx_t* legacy_device_info_ctx(void) { - const iolink_device_info_t* info = iolink_device_info_get(); - if (info == NULL) { - return 0U; + if (!g_legacy_device_info_ctx_initialized) { + iolink_device_info_ctx_init(&g_legacy_device_info_ctx, NULL); + g_legacy_device_info_ctx_initialized = true; } - return info->access_locks; + return &g_legacy_device_info_ctx; +} + +void iolink_device_info_init(const iolink_device_info_t* info) +{ + iolink_device_info_ctx_init(&g_legacy_device_info_ctx, info); + g_legacy_device_info_ctx_initialized = true; +} + +int iolink_device_info_set_application_tag(const char* tag, uint8_t len) +{ + return iolink_device_info_ctx_set_application_tag(legacy_device_info_ctx(), tag, len); +} + +const iolink_device_info_t* iolink_device_info_get(void) +{ + return iolink_device_info_ctx_get(legacy_device_info_ctx()); +} + +uint16_t iolink_device_info_get_access_locks(void) +{ + return iolink_device_info_ctx_get_access_locks(legacy_device_info_ctx()); } void iolink_device_info_set_access_locks(uint16_t locks) { - /* Only allow modification if using default info */ - if (g_device_info == &g_default_info) { - g_default_info.access_locks = locks; - } + iolink_device_info_ctx_set_access_locks(legacy_device_info_ctx(), locks); } diff --git a/src/dll.c b/src/dll.c index 29658dc..cddd9f4 100644 --- a/src/dll.c +++ b/src/dll.c @@ -24,7 +24,7 @@ static void dll_set_state(iolink_dll_ctx_t* ctx, iolink_dll_state_t new_state) if (ctx->state != new_state) { ctx->state = new_state; if (ctx->state_cb != NULL) { - ctx->state_cb(new_state); + ctx->state_cb(ctx->state_cb_user, new_state); } } } @@ -88,7 +88,7 @@ static bool dll_drain_rx(iolink_dll_ctx_t* ctx) } bool saw_byte = false; uint8_t byte = 0U; - while (ctx->phy->recv_byte(&byte) > 0) { + while (ctx->phy->recv_byte(ctx->phy->user, &byte) > 0) { saw_byte = true; } return saw_byte; @@ -144,7 +144,7 @@ static void dll_handle_operate_type0(iolink_dll_ctx_t* ctx, uint8_t mc, uint8_t resp[0] = od_resp; resp[1] = iolink_checksum_ck(resp[0], 0U); if (ctx->phy->send != NULL) { - ctx->phy->send(resp, 2); + ctx->phy->send(ctx->phy->user, resp, 2); } } @@ -188,7 +188,7 @@ static void dll_handle_operate_type1_2(iolink_dll_ctx_t* ctx) pos++; if (ctx->phy->send != NULL) { - ctx->phy->send(resp, pos); + ctx->phy->send(ctx->phy->user, resp, pos); ctx->fallback_count = 0U; uint32_t end_tx_us = (uint32_t) iolink_time_get_us(); ctx->response_time_us = end_tx_us - (uint32_t) ctx->last_cycle_start_us; @@ -213,7 +213,7 @@ static void dll_poll_diagnostics(iolink_dll_ctx_t* ctx) } if (ctx->phy->get_voltage_mv != NULL) { - int voltage = ctx->phy->get_voltage_mv(); + int voltage = ctx->phy->get_voltage_mv(ctx->phy->user); if ((voltage < 18000) || (voltage > 30000)) { ctx->voltage_faults++; iolink_event_trigger(&ctx->events, IOLINK_EVENT_PHY_VOLTAGE_FAULT, @@ -222,7 +222,7 @@ static void dll_poll_diagnostics(iolink_dll_ctx_t* ctx) } if (ctx->phy->is_short_circuit != NULL) { - if (ctx->phy->is_short_circuit()) { + if (ctx->phy->is_short_circuit(ctx->phy->user)) { ctx->short_circuits++; iolink_event_trigger(&ctx->events, IOLINK_EVENT_PHY_SHORT_CIRCUIT, IOLINK_EVENT_TYPE_ERROR); @@ -256,7 +256,7 @@ void iolink_dll_init(iolink_dll_ctx_t* ctx, const iolink_phy_api_t* phy) ctx->baudrate = IOLINK_BAUDRATE_COM2; if (ctx->phy->set_baudrate != NULL) { - ctx->phy->set_baudrate(IOLINK_BAUDRATE_COM2); + ctx->phy->set_baudrate(ctx->phy->user, IOLINK_BAUDRATE_COM2); } iolink_events_init(&ctx->events); @@ -325,7 +325,7 @@ void iolink_dll_process(iolink_dll_ctx_t* ctx) if (ctx->phy_mode == IOLINK_PHY_MODE_SIO) { if ((ctx->frame_index == 0U) && (ctx->phy->detect_wakeup != NULL)) { - if (ctx->phy->detect_wakeup() > 0) { + if (ctx->phy->detect_wakeup(ctx->phy->user) > 0) { ctx->wakeup_seen = true; dll_set_state(ctx, IOLINK_DLL_STATE_AWAITING_COMM); ctx->wakeup_deadline_us = iolink_time_get_us() + IOLINK_T_DWU_US; @@ -361,7 +361,7 @@ void iolink_dll_process(iolink_dll_ctx_t* ctx) } uint8_t byte; - while ((ctx->phy->recv_byte != NULL) && (ctx->phy->recv_byte(&byte) > 0)) { + while ((ctx->phy->recv_byte != NULL) && (ctx->phy->recv_byte(ctx->phy->user, &byte) > 0)) { uint64_t now_us = iolink_time_get_us(); ctx->last_activity_ms = iolink_time_get_ms(); if ((ctx->frame_index > 0U) && (ctx->enforce_timing) && (ctx->t_byte_limit_us > 0U)) { @@ -515,7 +515,7 @@ int iolink_dll_set_baudrate(iolink_dll_ctx_t* ctx, iolink_baudrate_t baudrate) { if (ctx == NULL) return -1; ctx->baudrate = baudrate; - if (ctx->phy->set_baudrate != NULL) ctx->phy->set_baudrate(baudrate); + if (ctx->phy->set_baudrate != NULL) ctx->phy->set_baudrate(ctx->phy->user, baudrate); ctx->t_ren_limit_us = dll_get_t_ren_limit_us(ctx); ctx->t_byte_limit_us = dll_get_t_byte_limit_us(ctx); return 0; @@ -541,7 +541,7 @@ void iolink_dll_get_pd_length(const iolink_dll_ctx_t* ctx, uint8_t* pd_in_len, u int iolink_dll_set_sio_mode(iolink_dll_ctx_t* ctx) { if (ctx == NULL) return -1; - if (ctx->phy->set_mode != NULL) ctx->phy->set_mode(IOLINK_PHY_MODE_SIO); + if (ctx->phy->set_mode != NULL) ctx->phy->set_mode(ctx->phy->user, IOLINK_PHY_MODE_SIO); ctx->phy_mode = IOLINK_PHY_MODE_SIO; return 0; } @@ -549,7 +549,7 @@ int iolink_dll_set_sio_mode(iolink_dll_ctx_t* ctx) int iolink_dll_set_sdci_mode(iolink_dll_ctx_t* ctx) { if (ctx == NULL) return -1; - if (ctx->phy->set_mode != NULL) ctx->phy->set_mode(IOLINK_PHY_MODE_SDCI); + if (ctx->phy->set_mode != NULL) ctx->phy->set_mode(ctx->phy->user, IOLINK_PHY_MODE_SDCI); ctx->phy_mode = IOLINK_PHY_MODE_SDCI; return 0; } diff --git a/src/iolink_core.c b/src/iolink_core.c deleted file mode 100644 index 958b800..0000000 --- a/src/iolink_core.c +++ /dev/null @@ -1,261 +0,0 @@ -/* - * Copyright (C) 2026 Andrii Shylenko - * SPDX-License-Identifier: GPL-3.0-or-later - * - * This file is part of iolinki. - * See LICENSE for details. - */ - -#include "iolinki/iolink.h" -#include "iolinki/dll.h" -#include "iolinki/application.h" -#include "iolinki/data_storage.h" -#include "iolinki/params.h" -#include "iolinki/platform.h" -#include "iolinki/time_utils.h" -#include - -static iolink_dll_ctx_t g_dll_ctx; -static iolink_config_t g_config; -static iolink_reset_handler_t g_reset_handler; -static const iolink_app_callbacks_t* g_app_callbacks; - -/* Trampoline bound to the DLL state-change hook; dispatches to the application - lifecycle callbacks so no transition is missed. */ -static void core_state_cb(iolink_dll_state_t state) -{ - if (g_app_callbacks == NULL) { - return; - } - switch (state) { - case IOLINK_DLL_STATE_STARTUP: - if (g_app_callbacks->on_startup != NULL) { - g_app_callbacks->on_startup(); - } - break; - case IOLINK_DLL_STATE_PREOPERATE: - if (g_app_callbacks->on_preoperate != NULL) { - g_app_callbacks->on_preoperate(); - } - break; - case IOLINK_DLL_STATE_OPERATE: - if (g_app_callbacks->on_operate != NULL) { - g_app_callbacks->on_operate(); - } - break; - default: - break; - } -} - -int iolink_init(const iolink_phy_api_t* phy, const iolink_config_t* config) -{ - if (phy == NULL) { - return -1; - } - - if (config != NULL) { - (void) memcpy(&g_config, config, sizeof(iolink_config_t)); - } - else { - /* Default config */ - (void) memset(&g_config, 0, sizeof(iolink_config_t)); - g_config.m_seq_type = IOLINK_M_SEQ_TYPE_0; - g_config.min_cycle_time = 0U; /* Min */ - } - - if (phy->init != NULL) { - int err = phy->init(); - if (err != 0) { - return err; - } - } - - iolink_dll_init(&g_dll_ctx, phy); - g_dll_ctx.state_cb = core_state_cb; - iolink_params_init(); - g_dll_ctx.m_seq_type = (uint8_t) g_config.m_seq_type; - g_dll_ctx.pd_in_len = g_config.pd_in_len; - g_dll_ctx.pd_out_len = g_config.pd_out_len; - g_dll_ctx.min_cycle_time_us = (uint32_t) g_config.min_cycle_time * 100U; /* 0.1ms units */ - g_dll_ctx.t_pd_delay_us = g_config.t_pd_us; - if (g_dll_ctx.t_pd_delay_us > 0U) { - g_dll_ctx.t_pd_deadline_us = iolink_time_get_us() + (uint64_t) g_dll_ctx.t_pd_delay_us; - } - else { - g_dll_ctx.t_pd_deadline_us = 0U; - } - - /* Apply config-dependent DLL fields (must run after m_seq_type is set) */ - if ((g_dll_ctx.m_seq_type == IOLINK_M_SEQ_TYPE_2_1) || - g_dll_ctx.m_seq_type == IOLINK_M_SEQ_TYPE_2_2 || - g_dll_ctx.m_seq_type == IOLINK_M_SEQ_TYPE_2_V) { - g_dll_ctx.od_len = 2U; - } - else { - g_dll_ctx.od_len = 1U; - } - - if ((g_dll_ctx.m_seq_type == IOLINK_M_SEQ_TYPE_1_V) || - g_dll_ctx.m_seq_type == IOLINK_M_SEQ_TYPE_2_V) { - g_dll_ctx.pd_in_len_current = g_dll_ctx.pd_in_len; - g_dll_ctx.pd_out_len_current = g_dll_ctx.pd_out_len; - g_dll_ctx.pd_in_len_max = g_dll_ctx.pd_in_len; - g_dll_ctx.pd_out_len_max = g_dll_ctx.pd_out_len; - } - else { - g_dll_ctx.pd_in_len_current = g_dll_ctx.pd_in_len; - g_dll_ctx.pd_out_len_current = g_dll_ctx.pd_out_len; - g_dll_ctx.pd_in_len_max = g_dll_ctx.pd_in_len; - g_dll_ctx.pd_out_len_max = g_dll_ctx.pd_out_len; - } - - /* Announce the initial state to a callback registered before init. */ - if ((g_app_callbacks != NULL) && (g_app_callbacks->on_startup != NULL)) { - g_app_callbacks->on_startup(); - } - - return 0; -} - -void iolink_app_register(const iolink_app_callbacks_t* callbacks) -{ - g_app_callbacks = callbacks; -} - -void iolink_set_reset_handler(iolink_reset_handler_t handler) -{ - g_reset_handler = handler; -} - -void iolink_process(void) -{ - iolink_dll_process(&g_dll_ctx); - - /* Consume Master-issued reset requests recorded during ISDU processing. - Cleared unconditionally so a request is acted on exactly once. */ - if (g_dll_ctx.isdu.reset_pending) { - g_dll_ctx.isdu.reset_pending = false; - if (g_reset_handler != NULL) { - g_reset_handler(IOLINK_RESET_DEVICE); - } - } - if (g_dll_ctx.isdu.app_reset_pending) { - g_dll_ctx.isdu.app_reset_pending = false; - if (g_reset_handler != NULL) { - g_reset_handler(IOLINK_RESET_APPLICATION); - } - } - - /* Cyclic Process Data callbacks while in OPERATE. */ - if ((g_app_callbacks != NULL) && (g_dll_ctx.state == IOLINK_DLL_STATE_OPERATE)) { - if (g_app_callbacks->on_pd_output != NULL) { - g_app_callbacks->on_pd_output(g_dll_ctx.pd_out, g_dll_ctx.pd_out_len_current); - } - if (g_app_callbacks->on_pd_input != NULL) { - g_app_callbacks->on_pd_input(g_dll_ctx.pd_in, g_dll_ctx.pd_in_len_current); - } - } -} - -int iolink_pd_input_update(const uint8_t* data, size_t len, bool valid) -{ - if (data == NULL) { - return -1; - } - if (len > sizeof(g_dll_ctx.pd_in)) { - return -1; - } - - iolink_critical_enter(); - (void) memcpy(g_dll_ctx.pd_in, data, len); - g_dll_ctx.pd_in_len = (uint8_t) len; - g_dll_ctx.pd_valid = valid; - g_dll_ctx.pd_in_toggle = !g_dll_ctx.pd_in_toggle; - iolink_critical_exit(); - - return 0; -} - -int iolink_pd_output_read(uint8_t* data, size_t len) -{ - if (data == NULL) { - return -1; - } - - iolink_critical_enter(); - uint8_t read_len = (len < g_dll_ctx.pd_out_len) ? (uint8_t) len : g_dll_ctx.pd_out_len; - (void) memcpy(data, g_dll_ctx.pd_out, read_len); - iolink_critical_exit(); - - return (int) read_len; -} - -iolink_events_ctx_t* iolink_get_events_ctx(void) -{ - return &g_dll_ctx.events; -} - -iolink_ds_ctx_t* iolink_get_ds_ctx(void) -{ - return &g_dll_ctx.ds; -} - -iolink_dll_state_t iolink_get_state(void) -{ - return g_dll_ctx.state; -} - -iolink_phy_mode_t iolink_get_phy_mode(void) -{ - return g_dll_ctx.phy_mode; -} - -iolink_baudrate_t iolink_get_baudrate(void) -{ - return g_dll_ctx.baudrate; -} - -void iolink_get_dll_stats(iolink_dll_stats_t* out_stats) -{ - iolink_dll_get_stats(&g_dll_ctx, out_stats); -} - -void iolink_set_timing_enforcement(bool enable) -{ - iolink_dll_set_timing_enforcement(&g_dll_ctx, enable); -} - -void iolink_set_t_ren_limit_us(uint32_t limit_us) -{ - iolink_dll_set_t_ren_limit_us(&g_dll_ctx, limit_us); -} - -iolink_m_seq_type_t iolink_get_m_seq_type(void) -{ - return (iolink_m_seq_type_t) g_dll_ctx.m_seq_type; -} - -uint8_t iolink_get_pd_in_len(void) -{ - /* Current length: equals the configured value for fixed types, and tracks - runtime changes for variable (TYPE_1_V / TYPE_2_V) M-sequences. */ - return g_dll_ctx.pd_in_len_current; -} - -uint8_t iolink_get_pd_out_len(void) -{ - return g_dll_ctx.pd_out_len_current; -} - -int iolink_set_pd_length(uint8_t pd_in_len, uint8_t pd_out_len) -{ - if ((g_dll_ctx.m_seq_type != IOLINK_M_SEQ_TYPE_1_V) && - (g_dll_ctx.m_seq_type != IOLINK_M_SEQ_TYPE_2_V)) { - return -2; /* Fixed-length M-sequence: PD length is not negotiable */ - } - if ((pd_in_len > g_dll_ctx.pd_in_len_max) || (pd_out_len > g_dll_ctx.pd_out_len_max)) { - return -1; /* Exceeds configured maximum */ - } - return iolink_dll_set_pd_length(&g_dll_ctx, pd_in_len, pd_out_len); -} diff --git a/src/isdu.c b/src/isdu.c index 0ad6508..79c6cc1 100644 --- a/src/isdu.c +++ b/src/isdu.c @@ -43,6 +43,24 @@ void iolink_isdu_init(iolink_isdu_ctx_t* ctx) ctx->next_state = ISDU_STATE_IDLE; } +static int isdu_params_get(const iolink_isdu_ctx_t* ctx, uint16_t index, uint8_t subindex, + uint8_t* buffer, size_t max_len) +{ + if ((ctx != NULL) && (ctx->params_ctx != NULL)) { + return iolink_params_ctx_get(ctx->params_ctx, index, subindex, buffer, max_len); + } + return iolink_params_get(index, subindex, buffer, max_len); +} + +static int isdu_params_set(iolink_isdu_ctx_t* ctx, uint16_t index, uint8_t subindex, + const uint8_t* data, size_t len, bool persist) +{ + if ((ctx != NULL) && (ctx->params_ctx != NULL)) { + return iolink_params_ctx_set(ctx->params_ctx, index, subindex, data, len, persist); + } + return iolink_params_set(index, subindex, data, len, persist); +} + static int isdu_handle_idle(iolink_isdu_ctx_t* ctx, uint8_t byte) { bool start = ((byte & IOLINK_ISDU_CTRL_START) != 0U); @@ -295,8 +313,8 @@ static void handle_mandatory_indices(iolink_isdu_ctx_t* ctx) case IOLINK_IDX_APPLICATION_TAG: if (ctx->header.type == IOLINK_ISDU_SERVICE_TYPE_WRITE) { - if (iolink_params_set(IOLINK_IDX_APPLICATION_TAG, 0U, ctx->buffer, ctx->buffer_idx, - true) == 0) { + if (isdu_params_set(ctx, IOLINK_IDX_APPLICATION_TAG, 0U, ctx->buffer, + ctx->buffer_idx, true) == 0) { ctx->response_len = 0U; ctx->response_idx = 0U; ctx->state = ISDU_STATE_RESPONSE_READY; @@ -305,8 +323,8 @@ static void handle_mandatory_indices(iolink_isdu_ctx_t* ctx) } } else { - int res = iolink_params_get(IOLINK_IDX_APPLICATION_TAG, 0U, ctx->response_buf, - (size_t) IOLINK_ISDU_BUFFER_SIZE); + int res = isdu_params_get(ctx, IOLINK_IDX_APPLICATION_TAG, 0U, ctx->response_buf, + (size_t) IOLINK_ISDU_BUFFER_SIZE); if (res >= 0) { ctx->response_len = (uint8_t) res; ctx->response_idx = 0U; @@ -319,8 +337,8 @@ static void handle_mandatory_indices(iolink_isdu_ctx_t* ctx) case IOLINK_IDX_FUNCTION_TAG: if (ctx->header.type == IOLINK_ISDU_SERVICE_TYPE_WRITE) { - if (iolink_params_set(IOLINK_IDX_FUNCTION_TAG, 0U, ctx->buffer, ctx->buffer_idx, - true) == 0) { + if (isdu_params_set(ctx, IOLINK_IDX_FUNCTION_TAG, 0U, ctx->buffer, ctx->buffer_idx, + true) == 0) { ctx->response_len = 0U; ctx->response_idx = 0U; ctx->state = ISDU_STATE_RESPONSE_READY; @@ -329,8 +347,8 @@ static void handle_mandatory_indices(iolink_isdu_ctx_t* ctx) } } else { - int res = iolink_params_get(IOLINK_IDX_FUNCTION_TAG, 0U, ctx->response_buf, - (size_t) IOLINK_ISDU_BUFFER_SIZE); + int res = isdu_params_get(ctx, IOLINK_IDX_FUNCTION_TAG, 0U, ctx->response_buf, + (size_t) IOLINK_ISDU_BUFFER_SIZE); if (res >= 0) { ctx->response_len = (uint8_t) res; ctx->response_idx = 0U; @@ -343,8 +361,8 @@ static void handle_mandatory_indices(iolink_isdu_ctx_t* ctx) case IOLINK_IDX_LOCATION_TAG: if (ctx->header.type == IOLINK_ISDU_SERVICE_TYPE_WRITE) { - if (iolink_params_set(IOLINK_IDX_LOCATION_TAG, 0U, ctx->buffer, ctx->buffer_idx, - true) == 0) { + if (isdu_params_set(ctx, IOLINK_IDX_LOCATION_TAG, 0U, ctx->buffer, ctx->buffer_idx, + true) == 0) { ctx->response_len = 0U; ctx->response_idx = 0U; ctx->state = ISDU_STATE_RESPONSE_READY; @@ -352,8 +370,8 @@ static void handle_mandatory_indices(iolink_isdu_ctx_t* ctx) } } else { - int res = iolink_params_get(IOLINK_IDX_LOCATION_TAG, 0U, ctx->response_buf, - (size_t) IOLINK_ISDU_BUFFER_SIZE); + int res = isdu_params_get(ctx, IOLINK_IDX_LOCATION_TAG, 0U, ctx->response_buf, + (size_t) IOLINK_ISDU_BUFFER_SIZE); if (res >= 0) { ctx->response_len = (uint8_t) res; ctx->response_idx = 0U; @@ -662,9 +680,16 @@ static void handle_error_stats(iolink_isdu_ctx_t* ctx) ctx->state = ISDU_STATE_RESPONSE_READY; } -/* Direct Parameter page 2 (device-specific, addresses 0x10-0x1F). RAM-backed; - single-instance to match the stack's existing global model. */ -static uint8_t g_direct_param_page2[16]; +/* Direct Parameter page 2 (device-specific, addresses 0x10-0x1F). */ +static uint8_t* direct_param_page2(iolink_isdu_ctx_t* ctx) +{ + static uint8_t fallback_page2[16] = {0U}; + + if ((ctx != NULL) && (ctx->direct_param_page2 != NULL)) { + return (uint8_t*) ctx->direct_param_page2; + } + return fallback_page2; +} /* Encode a Process Data length (in octets) per IO-Link V1.1.5 Figure B.5: <=2 octets are expressed as bit length (BYTE=0); larger as octets (BYTE=1). */ @@ -736,6 +761,7 @@ static void handle_direct_parameters(iolink_isdu_ctx_t* ctx) { bool page2 = (ctx->header.index == IOLINK_IDX_DIRECT_PARAMETERS_2); uint8_t sub = ctx->header.subindex; + uint8_t* page2_storage = direct_param_page2(ctx); if (ctx->header.type == IOLINK_ISDU_SERVICE_TYPE_WRITE) { if (!page2) { @@ -746,14 +772,14 @@ static void handle_direct_parameters(iolink_isdu_ctx_t* ctx) } else if (sub == 0U) { size_t n = ctx->buffer_idx; - if (n > sizeof(g_direct_param_page2)) { - n = sizeof(g_direct_param_page2); + if (n > 16U) { + n = 16U; } - (void) memcpy(g_direct_param_page2, ctx->buffer, n); + (void) memcpy(page2_storage, ctx->buffer, n); ctx->response_len = 0U; } else if ((sub <= 16U) && (ctx->buffer_idx >= 1U)) { - g_direct_param_page2[sub - 1U] = ctx->buffer[0]; + page2_storage[sub - 1U] = ctx->buffer[0]; ctx->response_len = 0U; } else { @@ -765,7 +791,7 @@ static void handle_direct_parameters(iolink_isdu_ctx_t* ctx) else { uint8_t page[16]; if (page2) { - (void) memcpy(page, g_direct_param_page2, sizeof(page)); + (void) memcpy(page, page2_storage, sizeof(page)); } else { build_direct_param_page1(ctx, page); diff --git a/src/params.c b/src/params.c index 3788531..31faa0c 100644 --- a/src/params.c +++ b/src/params.c @@ -8,7 +8,7 @@ #include "iolinki/params.h" #include "iolinki/platform.h" -#include "iolinki/device_info.h" +#include "iolinki/protocol.h" #include "iolinki/utils.h" #include @@ -23,128 +23,217 @@ typedef struct /* Future: user parameters, etc. */ } iolink_params_nvm_t; -static iolink_params_nvm_t g_nvm_shadow; +static iolink_device_info_ctx_t g_legacy_device_info_ctx; +static iolink_params_ctx_t g_legacy_params_ctx; +static bool g_legacy_params_ctx_initialized = false; -void iolink_params_init(void) +static void copy_tag(char* dst, bool* valid, const uint8_t* data, size_t len) { - /* Try to load from NVM */ - if (iolink_nvm_read(0U, (uint8_t*) &g_nvm_shadow, sizeof(g_nvm_shadow)) == 0) { - if (g_nvm_shadow.magic == PARAMS_NVM_MAGIC) { - /* Sync with device info */ - (void) iolink_device_info_set_application_tag( - g_nvm_shadow.application_tag, (uint8_t) strlen(g_nvm_shadow.application_tag)); - return; - } + size_t copy_len = (len > 32U) ? 32U : len; + + if (copy_len > 0U) { + (void) memcpy(dst, data, copy_len); + } + dst[copy_len] = '\0'; + *valid = true; +} + +static size_t bounded_tag_len(const char* tag) +{ + size_t len = strlen(tag); + if (len > 32U) { + len = 32U; + } + return len; +} + +static int read_tag(const char* tag, uint8_t* buffer, size_t max_len) +{ + size_t len = bounded_tag_len(tag); + if (len > max_len) { + len = max_len; + } + if (len > 0U) { + (void) memcpy(buffer, tag, len); } + return (int) len; +} - /* Init default state */ - g_nvm_shadow.magic = PARAMS_NVM_MAGIC; - const iolink_device_info_t* info = iolink_device_info_get(); - if ((info != NULL) && (info->application_tag != NULL)) { - size_t copy_len = strlen(info->application_tag); - if (copy_len > 32U) { - copy_len = 32U; +static void params_ctx_write_nvm(const iolink_params_ctx_t* ctx) +{ + iolink_params_nvm_t nvm; + + (void) memset(&nvm, 0, sizeof(nvm)); + nvm.magic = PARAMS_NVM_MAGIC; + if (ctx->application_tag_valid) { + (void) memcpy(nvm.application_tag, ctx->application_tag, sizeof(nvm.application_tag)); + } + if (ctx->function_tag_valid) { + (void) memcpy(nvm.function_tag, ctx->function_tag, sizeof(nvm.function_tag)); + } + if (ctx->location_tag_valid) { + (void) memcpy(nvm.location_tag, ctx->location_tag, sizeof(nvm.location_tag)); + } + (void) iolink_nvm_write(0U, (uint8_t*) &nvm, sizeof(nvm)); +} + +void iolink_params_ctx_init(iolink_params_ctx_t* ctx, iolink_device_info_ctx_t* device_info) +{ + iolink_params_nvm_t nvm; + + if (ctx == NULL) { + return; + } + + (void) memset(ctx, 0, sizeof(*ctx)); + ctx->device_info = device_info; + + if ((iolink_nvm_read(0U, (uint8_t*) &nvm, sizeof(nvm)) == 0) && + (nvm.magic == PARAMS_NVM_MAGIC)) { + copy_tag(ctx->application_tag, &ctx->application_tag_valid, + (const uint8_t*) nvm.application_tag, bounded_tag_len(nvm.application_tag)); + copy_tag(ctx->function_tag, &ctx->function_tag_valid, (const uint8_t*) nvm.function_tag, + bounded_tag_len(nvm.function_tag)); + copy_tag(ctx->location_tag, &ctx->location_tag_valid, (const uint8_t*) nvm.location_tag, + bounded_tag_len(nvm.location_tag)); + if (ctx->device_info != NULL) { + (void) iolink_device_info_ctx_set_application_tag( + ctx->device_info, ctx->application_tag, (uint8_t) strlen(ctx->application_tag)); } - (void) memcpy(g_nvm_shadow.application_tag, info->application_tag, copy_len); - g_nvm_shadow.application_tag[copy_len] = '\0'; + return; } - else { - g_nvm_shadow.application_tag[0] = '\0'; + + if (ctx->device_info != NULL) { + const iolink_device_info_t* info = iolink_device_info_ctx_get(ctx->device_info); + if ((info != NULL) && (info->application_tag != NULL)) { + copy_tag(ctx->application_tag, &ctx->application_tag_valid, + (const uint8_t*) info->application_tag, + bounded_tag_len(info->application_tag)); + } } - g_nvm_shadow.function_tag[0] = '\0'; - g_nvm_shadow.location_tag[0] = '\0'; } -int iolink_params_get(uint16_t index, uint8_t subindex, uint8_t* buffer, size_t max_len) +int iolink_params_ctx_get(const iolink_params_ctx_t* ctx, uint16_t index, uint8_t subindex, + uint8_t* buffer, size_t max_len) { - if (buffer == NULL) { + if ((ctx == NULL) || (buffer == NULL)) { return -1; } - if ((index == 0x0018U) && (subindex == 0U)) { - const iolink_device_info_t* info = iolink_device_info_get(); + if ((index == IOLINK_IDX_APPLICATION_TAG) && (subindex == 0U)) { + const iolink_device_info_t* info = iolink_device_info_ctx_get(ctx->device_info); if ((info != NULL) && (info->application_tag != NULL)) { - size_t len = strlen(info->application_tag); - if (len > max_len) { - len = max_len; - } - (void) memcpy(buffer, info->application_tag, len); - return (int) len; + return read_tag(info->application_tag, buffer, max_len); } + return 0; } - if ((index == 0x0019U) && (subindex == 0U)) { - size_t len = strlen(g_nvm_shadow.function_tag); - if (len > max_len) { - len = max_len; + if ((index == IOLINK_IDX_FUNCTION_TAG) && (subindex == 0U)) { + if (!ctx->function_tag_valid) { + return 0; } - (void) memcpy(buffer, g_nvm_shadow.function_tag, len); - return (int) len; + return read_tag(ctx->function_tag, buffer, max_len); } - if ((index == 0x001AU) && (subindex == 0U)) { - size_t len = strlen(g_nvm_shadow.location_tag); - if (len > max_len) { - len = max_len; + if ((index == IOLINK_IDX_LOCATION_TAG) && (subindex == 0U)) { + if (!ctx->location_tag_valid) { + return 0; } - (void) memcpy(buffer, g_nvm_shadow.location_tag, len); - return (int) len; + return read_tag(ctx->location_tag, buffer, max_len); } return -1; } -int iolink_params_set(uint16_t index, uint8_t subindex, const uint8_t* data, size_t len, - bool persist) +int iolink_params_ctx_set(iolink_params_ctx_t* ctx, uint16_t index, uint8_t subindex, + const uint8_t* data, size_t len, bool persist) { - if (!iolink_buf_is_valid(data, len)) { + if ((ctx == NULL) || !iolink_buf_is_valid(data, len)) { return -1; } - if ((index == 0x0018U) && (subindex == 0U)) { - if (iolink_device_info_set_application_tag((const char*) data, (uint8_t) len) == 0) { - if (persist) { - size_t copy_len = (len > 32U) ? 32U : len; - if (copy_len > 0U) { - (void) memcpy(g_nvm_shadow.application_tag, data, copy_len); - } - g_nvm_shadow.application_tag[copy_len] = '\0'; - (void) iolink_nvm_write(0U, (uint8_t*) &g_nvm_shadow, sizeof(g_nvm_shadow)); - } - return 0; + if ((index == IOLINK_IDX_APPLICATION_TAG) && (subindex == 0U)) { + if (len >= sizeof(ctx->application_tag)) { + return -1; } - } - if ((index == 0x0019U) && (subindex == 0U)) { - size_t copy_len = (len > 32U) ? 32U : len; - if (copy_len > 0U) { - (void) memcpy(g_nvm_shadow.function_tag, data, copy_len); + copy_tag(ctx->application_tag, &ctx->application_tag_valid, data, len); + if (ctx->device_info != NULL) { + if (iolink_device_info_ctx_set_application_tag( + ctx->device_info, ctx->application_tag, + (uint8_t) strlen(ctx->application_tag)) != 0) { + return -1; + } } - g_nvm_shadow.function_tag[copy_len] = '\0'; if (persist) { - (void) iolink_nvm_write(0U, (uint8_t*) &g_nvm_shadow, sizeof(g_nvm_shadow)); + params_ctx_write_nvm(ctx); } return 0; } - if ((index == 0x001AU) && (subindex == 0U)) { - size_t copy_len = (len > 32U) ? 32U : len; - if (copy_len > 0U) { - (void) memcpy(g_nvm_shadow.location_tag, data, copy_len); + if ((index == IOLINK_IDX_FUNCTION_TAG) && (subindex == 0U)) { + copy_tag(ctx->function_tag, &ctx->function_tag_valid, data, len); + if (persist) { + params_ctx_write_nvm(ctx); } - g_nvm_shadow.location_tag[copy_len] = '\0'; + return 0; + } + if ((index == IOLINK_IDX_LOCATION_TAG) && (subindex == 0U)) { + copy_tag(ctx->location_tag, &ctx->location_tag_valid, data, len); if (persist) { - (void) iolink_nvm_write(0U, (uint8_t*) &g_nvm_shadow, sizeof(g_nvm_shadow)); + params_ctx_write_nvm(ctx); } return 0; } return -1; } -void iolink_params_factory_reset(void) +void iolink_params_ctx_factory_reset(iolink_params_ctx_t* ctx) { - /* Reset to factory defaults */ - g_nvm_shadow.magic = PARAMS_NVM_MAGIC; - g_nvm_shadow.application_tag[0] = '\0'; - g_nvm_shadow.function_tag[0] = '\0'; - g_nvm_shadow.location_tag[0] = '\0'; + if (ctx == NULL) { + return; + } - /* Clear device info application tag */ - (void) iolink_device_info_set_application_tag("", 0U); + ctx->application_tag[0] = '\0'; + ctx->function_tag[0] = '\0'; + ctx->location_tag[0] = '\0'; + ctx->application_tag_valid = true; + ctx->function_tag_valid = true; + ctx->location_tag_valid = true; + + if (ctx->device_info != NULL) { + (void) iolink_device_info_ctx_set_application_tag(ctx->device_info, "", 0U); + } + params_ctx_write_nvm(ctx); +} + +static iolink_params_ctx_t* legacy_params_ctx(void) +{ + if (!g_legacy_params_ctx_initialized) { + iolink_device_info_ctx_init(&g_legacy_device_info_ctx, NULL); + iolink_params_ctx_init(&g_legacy_params_ctx, &g_legacy_device_info_ctx); + g_legacy_params_ctx_initialized = true; + } + return &g_legacy_params_ctx; +} - /* Erase NVM (write zeros or default structure) */ - (void) iolink_nvm_write(0U, (uint8_t*) &g_nvm_shadow, sizeof(g_nvm_shadow)); +void iolink_params_init(void) +{ + iolink_device_info_ctx_init(&g_legacy_device_info_ctx, iolink_device_info_get()); + iolink_params_ctx_init(&g_legacy_params_ctx, &g_legacy_device_info_ctx); + g_legacy_params_ctx_initialized = true; +} + +int iolink_params_get(uint16_t index, uint8_t subindex, uint8_t* buffer, size_t max_len) +{ + return iolink_params_ctx_get(legacy_params_ctx(), index, subindex, buffer, max_len); +} + +int iolink_params_set(uint16_t index, uint8_t subindex, const uint8_t* data, size_t len, + bool persist) +{ + int res = iolink_params_ctx_set(legacy_params_ctx(), index, subindex, data, len, persist); + if ((res == 0) && (index == IOLINK_IDX_APPLICATION_TAG) && (subindex == 0U)) { + (void) iolink_device_info_set_application_tag((const char*) data, (uint8_t) len); + } + return res; +} + +void iolink_params_factory_reset(void) +{ + iolink_params_ctx_factory_reset(legacy_params_ctx()); + (void) iolink_device_info_set_application_tag("", 0U); } diff --git a/src/phy_generic.c b/src/phy_generic.c index f87a612..136437a 100644 --- a/src/phy_generic.c +++ b/src/phy_generic.c @@ -8,59 +8,68 @@ #include "iolinki/phy_generic.h" -static int generic_init(void) +static int generic_init(void* user) { + (void) user; /* Template: initialize UART/GPIO/transceiver here. */ return -1; } -static void generic_set_mode(iolink_phy_mode_t mode) +static void generic_set_mode(void* user, iolink_phy_mode_t mode) { + (void) user; (void) mode; /* Template: configure transceiver for SIO/SDCI. */ } -static void generic_set_baudrate(iolink_baudrate_t baudrate) +static void generic_set_baudrate(void* user, iolink_baudrate_t baudrate) { + (void) user; (void) baudrate; /* Template: configure UART speed for COM1/2/3. */ } -static int generic_send(const uint8_t* data, size_t len) +static int generic_send(void* user, const uint8_t* data, size_t len) { + (void) user; (void) data; (void) len; /* Template: transmit data over C/Q line. */ return -1; } -static int generic_recv_byte(uint8_t* byte) +static int generic_recv_byte(void* user, uint8_t* byte) { + (void) user; (void) byte; /* Template: non-blocking receive. */ return 0; } -static int generic_detect_wakeup(void) +static int generic_detect_wakeup(void* user) { + (void) user; /* Template: detect 80us wake-up pulse on C/Q line. */ return 0; } -static void generic_set_cq_line(uint8_t state) +static void generic_set_cq_line(void* user, uint8_t state) { + (void) user; (void) state; /* Template: drive C/Q line high/low in SIO mode. */ } -static int generic_get_voltage_mv(void) +static int generic_get_voltage_mv(void* user) { + (void) user; /* Template: return supply voltage in mV, or negative if unavailable. */ return -1; } -static bool generic_is_short_circuit(void) +static bool generic_is_short_circuit(void* user) { + (void) user; /* Template: return true if fault detected. */ return false; } diff --git a/src/phy_virtual.c b/src/phy_virtual.c index 8ad0cc9..64ccab3 100644 --- a/src/phy_virtual.c +++ b/src/phy_virtual.c @@ -28,8 +28,10 @@ void iolink_phy_virtual_set_port(const char* port) g_port_path = port; } -static int virtual_init(void) +static int virtual_init(void* user) { + (void) user; + if (g_port_path == NULL) { printf("[PHY-VIRTUAL] Error: Port not set\n"); return -1; @@ -67,18 +69,22 @@ static int virtual_init(void) return 0; } -static void virtual_set_mode(iolink_phy_mode_t mode) +static void virtual_set_mode(void* user, iolink_phy_mode_t mode) { + (void) user; printf("[PHY-VIRTUAL] Mode set to: %d\n", (int) mode); } -static void virtual_set_baudrate(iolink_baudrate_t baudrate) +static void virtual_set_baudrate(void* user, iolink_baudrate_t baudrate) { + (void) user; printf("[PHY-VIRTUAL] Baudrate set to: %d\n", (int) baudrate); } -static int virtual_send(const uint8_t* data, size_t len) +static int virtual_send(void* user, const uint8_t* data, size_t len) { + (void) user; + if ((g_fd < 0) || (data == NULL)) { return -1; } @@ -88,8 +94,10 @@ static int virtual_send(const uint8_t* data, size_t len) return (int) write(g_fd, data, len); } -static int virtual_recv_byte(uint8_t* byte) +static int virtual_recv_byte(void* user, uint8_t* byte) { + (void) user; + if ((g_fd < 0) || (byte == NULL)) { return 0; } @@ -98,8 +106,10 @@ static int virtual_recv_byte(uint8_t* byte) return (n > 0) ? 1 : 0; } -static int virtual_detect_wakeup(void) +static int virtual_detect_wakeup(void* user) { + (void) user; + if (g_fd < 0) { return 0; } diff --git a/src/platform/zephyr/phy_uart.c b/src/platform/zephyr/phy_uart.c index 6accb0e..91edc0b 100644 --- a/src/platform/zephyr/phy_uart.c +++ b/src/platform/zephyr/phy_uart.c @@ -63,8 +63,10 @@ static void uart_phy_isr(const struct device* dev, void* user_data) } } -static int uart_phy_init(void) +static int uart_phy_init(void* user) { + (void) user; + if (g_uart_dev == NULL) { LOG_ERR("UART device not set; call iolink_phy_uart_init[_default]() first"); return -1; @@ -94,8 +96,10 @@ static int uart_phy_init(void) return 0; } -static void uart_phy_set_mode(iolink_phy_mode_t mode) +static void uart_phy_set_mode(void* user, iolink_phy_mode_t mode) { + (void) user; + /* * A plain UART cannot drive SIO/SDCI mode switching on the C/Q line; the * line discipline is owned by the transceiver front-end. The mode hint is @@ -104,8 +108,10 @@ static void uart_phy_set_mode(iolink_phy_mode_t mode) LOG_DBG("set_mode(%d): no-op for plain UART PHY", mode); } -static void uart_phy_set_baudrate(iolink_baudrate_t baudrate) +static void uart_phy_set_baudrate(void* user, iolink_baudrate_t baudrate) { + (void) user; + if (g_uart_dev == NULL) { return; } @@ -143,8 +149,10 @@ static void uart_phy_set_baudrate(iolink_baudrate_t baudrate) } } -static int uart_phy_send(const uint8_t* data, size_t len) +static int uart_phy_send(void* user, const uint8_t* data, size_t len) { + (void) user; + if (g_uart_dev == NULL) { return -1; } @@ -155,8 +163,10 @@ static int uart_phy_send(const uint8_t* data, size_t len) return (int) len; } -static int uart_phy_recv_byte(uint8_t* byte) +static int uart_phy_recv_byte(void* user, uint8_t* byte) { + (void) user; + if (g_uart_dev == NULL) { return -1; } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 4c7ce82..15391f2 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -37,6 +37,7 @@ if(CMOCKA_FOUND) add_iolink_test(test_integration_full test_integration_full.c) add_iolink_test(test_application test_application.c) add_iolink_test(test_m_sequence_types test_m_sequence_types.c) + add_iolink_test(test_reentrant_device test_reentrant_device.c) # Portability Verification add_iolink_test(test_locking test_locking.c) diff --git a/tests/test_app_pd.c b/tests/test_app_pd.c index 0b00e05..aeacf99 100644 --- a/tests/test_app_pd.c +++ b/tests/test_app_pd.c @@ -18,7 +18,7 @@ #include #include -#include "iolinki/iolink.h" +#include "iolinki/device.h" #include "iolinki/application.h" #include "iolinki/crc.h" #include "iolinki/protocol.h" @@ -58,10 +58,12 @@ static void test_pd_toggle_bit(void** state) { (void) state; iolink_config_t config = {.pd_in_len = 2, .pd_out_len = 2, .m_seq_type = IOLINK_M_SEQ_TYPE_2_2}; + iolink_test_device_t dev; + setup_mock_phy(); will_return(mock_phy_init, 0); - iolink_init(&g_phy_mock, &config); - move_to_operate(); + assert_int_equal(iolink_test_device_init(&dev, &config, NULL), 0); + move_to_operate_ctx(&dev.ctx); /* Helper variables for frame simulation */ uint8_t frame[7] = {0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; @@ -74,7 +76,7 @@ static void test_pd_toggle_bit(void** state) uint8_t input[2] = {0x11, 0x22}; /* Update 1: Valid=True. Flip 0->1. Toggle bit should be 1 (0x40). */ - iolink_pd_input_update(input, 2, true); + iolink_device_pd_input_update(&dev.ctx, input, 2, true); /* Simulate Frame */ for (int i = 0; i < 7; i++) { @@ -88,10 +90,10 @@ static void test_pd_toggle_bit(void** state) (void*) (uintptr_t) IOLINK_OD_STATUS_PD_TOGGLE); expect_value(mock_phy_send, len, 6); will_return(mock_phy_send, 0); - iolink_process(); + iolink_device_process(&dev.ctx); /* Update 2: Valid=True. Flip 1->0. Toggle bit should be 0 (0x00). */ - iolink_pd_input_update(input, 2, true); + iolink_device_pd_input_update(&dev.ctx, input, 2, true); /* Simulate Frame */ for (int i = 0; i < 7; i++) { @@ -104,10 +106,10 @@ static void test_pd_toggle_bit(void** state) expect_check(mock_phy_send, data, check_status_byte, (void*) (uintptr_t) 0x00); expect_value(mock_phy_send, len, 6); will_return(mock_phy_send, 0); - iolink_process(); + iolink_device_process(&dev.ctx); /* Update 3: Valid=True. Flip 0->1. Toggle bit should be 0x40. */ - iolink_pd_input_update(input, 2, true); + iolink_device_pd_input_update(&dev.ctx, input, 2, true); /* Simulate Frame */ for (int i = 0; i < 7; i++) { @@ -121,7 +123,7 @@ static void test_pd_toggle_bit(void** state) (void*) (uintptr_t) IOLINK_OD_STATUS_PD_TOGGLE); expect_value(mock_phy_send, len, 6); will_return(mock_phy_send, 0); - iolink_process(); + iolink_device_process(&dev.ctx); } int main(void) diff --git a/tests/test_application.c b/tests/test_application.c index 330125e..aed21c8 100644 --- a/tests/test_application.c +++ b/tests/test_application.c @@ -18,35 +18,39 @@ #include #include -#include "iolinki/iolink.h" +#include "iolinki/device.h" #include "iolinki/application.h" #include "test_helpers.h" static void test_pd_input_update_flow(void** state) { (void) state; + iolink_test_device_t dev; + setup_mock_phy(); will_return(mock_phy_init, 0); - iolink_init(&g_phy_mock, NULL); + assert_int_equal(iolink_test_device_init(&dev, NULL, NULL), 0); uint8_t data[] = {0xAA, 0xBB}; - int res = iolink_pd_input_update(data, sizeof(data), true); + int res = iolink_device_pd_input_update(&dev.ctx, data, sizeof(data), true); assert_int_equal(res, 0); /* Check internal state via output read (as simple proxy) */ - /* Note: iolink_pd_output_read reads FROM master, so this is not a direct mirror. + /* Note: PD output reads FROM master, so this is not a direct mirror. We just verify the API doesn't crash here. */ } static void test_pd_output_read_flow(void** state) { (void) state; + iolink_test_device_t dev; + setup_mock_phy(); will_return(mock_phy_init, 0); - iolink_init(&g_phy_mock, NULL); + assert_int_equal(iolink_test_device_init(&dev, NULL, NULL), 0); uint8_t buf[16]; - int res = iolink_pd_output_read(buf, sizeof(buf)); + int res = iolink_device_pd_output_read(&dev.ctx, buf, sizeof(buf)); /* Initial state should be 0 length or zeroed */ assert_int_equal(res, 0); } @@ -78,6 +82,8 @@ static void cb_pd_output(uint8_t* data, uint8_t len) static void test_app_callbacks_lifecycle(void** state) { (void) state; + iolink_test_device_t dev; + g_cb_startup = 0; g_cb_preoperate = 0; g_cb_operate = 0; @@ -92,24 +98,21 @@ static void test_app_callbacks_lifecycle(void** state) setup_mock_phy(); will_return(mock_phy_init, 0); - iolink_app_register(&cbs); - iolink_init(&g_phy_mock, NULL); + assert_int_equal(iolink_test_device_init(&dev, NULL, &cbs), 0); /* Registered before init -> initial STARTUP announced. */ assert_true(g_cb_startup >= 1); - move_to_operate(); + move_to_operate_ctx(&dev.ctx); /* PREOPERATE is traversed during the handshake; the state-change hook fires even though the transition is transient. */ assert_true(g_cb_preoperate >= 1); - if (iolink_get_state() == IOLINK_DLL_STATE_OPERATE) { + if (iolink_device_get_state(&dev.ctx) == IOLINK_DLL_STATE_OPERATE) { assert_true(g_cb_operate >= 1); assert_true(g_cb_pd_output >= 1); } - - iolink_app_register(NULL); /* Avoid dangling pointer across tests */ } int main(void) diff --git a/tests/test_baudrate.c b/tests/test_baudrate.c index c647fa9..c48623d 100644 --- a/tests/test_baudrate.c +++ b/tests/test_baudrate.c @@ -10,26 +10,31 @@ #include "iolinki/phy.h" /* Mock PHY functions */ -static int mock_init(void) +static int mock_init(void* user) { + (void) user; return 0; } -static void mock_set_mode(iolink_phy_mode_t mode) +static void mock_set_mode(void* user, iolink_phy_mode_t mode) { + (void) user; (void) mode; } -static void mock_set_baudrate(iolink_baudrate_t baudrate) +static void mock_set_baudrate(void* user, iolink_baudrate_t baudrate) { + (void) user; check_expected(baudrate); } -static int mock_send(const uint8_t* data, size_t len) +static int mock_send(void* user, const uint8_t* data, size_t len) { + (void) user; (void) data; (void) len; return (int) len; } -static int mock_recv_byte(uint8_t* byte) +static int mock_recv_byte(void* user, uint8_t* byte) { + (void) user; (void) byte; return 0; } diff --git a/tests/test_dll.c b/tests/test_dll.c index f16dba2..5dae1c3 100644 --- a/tests/test_dll.c +++ b/tests/test_dll.c @@ -19,7 +19,7 @@ #include #include -#include "iolinki/iolink.h" +#include "iolinki/device.h" #include "iolinki/dll.h" #include "iolinki/crc.h" #include "test_helpers.h" @@ -43,13 +43,14 @@ static void test_dll_wakeup_to_preoperate(void** state) (void) state; setup_mock_phy(); will_return(mock_phy_init, 0); - iolink_init(&g_phy_mock, NULL); - iolink_set_timing_enforcement(true); + iolink_test_device_t dev; + iolink_test_device_init(&dev, NULL, NULL); + iolink_device_set_timing_enforcement(&dev.ctx, true); /* Trigger wake-up */ iolink_phy_mock_set_wakeup(1); - iolink_process(); - assert_int_equal(iolink_get_state(), IOLINK_DLL_STATE_AWAITING_COMM); + iolink_device_process(&dev.ctx); + assert_int_equal(iolink_device_get_state(&dev.ctx), IOLINK_DLL_STATE_AWAITING_COMM); /* Wait for t_dwu to expire */ usleep(200); @@ -67,8 +68,8 @@ static void test_dll_wakeup_to_preoperate(void** state) expect_value(mock_phy_send, len, 2); will_return(mock_phy_send, 0); - iolink_process(); - assert_int_equal(iolink_get_state(), IOLINK_DLL_STATE_PREOPERATE); + iolink_device_process(&dev.ctx); + assert_int_equal(iolink_device_get_state(&dev.ctx), IOLINK_DLL_STATE_PREOPERATE); } static void test_dll_preoperate_to_operate(void** state) @@ -77,12 +78,13 @@ static void test_dll_preoperate_to_operate(void** state) iolink_config_t config = {.m_seq_type = IOLINK_M_SEQ_TYPE_1_1, .pd_in_len = 1, .pd_out_len = 1}; setup_mock_phy(); will_return(mock_phy_init, 0); - iolink_init(&g_phy_mock, &config); - iolink_set_timing_enforcement(true); + iolink_test_device_t dev; + iolink_test_device_init(&dev, &config, NULL); + iolink_device_set_timing_enforcement(&dev.ctx, true); /* Wake-up */ iolink_phy_mock_set_wakeup(1); - iolink_process(); + iolink_device_process(&dev.ctx); usleep(200); /* PREOPERATE -> ESTAB_COM */ @@ -94,8 +96,8 @@ static void test_dll_preoperate_to_operate(void** state) will_return(mock_phy_recv_byte, trans_ck); will_return(mock_phy_recv_byte, 0); /* No response for Transition Command in PREOPERATE */ - iolink_process(); - assert_int_equal(iolink_get_state(), IOLINK_DLL_STATE_ESTAB_COM); + iolink_device_process(&dev.ctx); + assert_int_equal(iolink_device_get_state(&dev.ctx), IOLINK_DLL_STATE_ESTAB_COM); /* ESTAB_COM -> OPERATE on first valid frame */ uint8_t frame[5] = {0x80, 0x00, 0x00, 0x00, 0x00}; @@ -110,8 +112,8 @@ static void test_dll_preoperate_to_operate(void** state) expect_value(mock_phy_send, len, 4); will_return(mock_phy_send, 0); - iolink_process(); - assert_int_equal(iolink_get_state(), IOLINK_DLL_STATE_OPERATE); + iolink_device_process(&dev.ctx); + assert_int_equal(iolink_device_get_state(&dev.ctx), IOLINK_DLL_STATE_OPERATE); } static void test_dll_fallback_on_crc_errors(void** state) @@ -120,12 +122,13 @@ static void test_dll_fallback_on_crc_errors(void** state) iolink_config_t config = {.m_seq_type = IOLINK_M_SEQ_TYPE_1_1, .pd_in_len = 1, .pd_out_len = 1}; setup_mock_phy(); will_return(mock_phy_init, 0); - iolink_init(&g_phy_mock, &config); - iolink_set_timing_enforcement(true); + iolink_test_device_t dev; + iolink_test_device_init(&dev, &config, NULL); + iolink_device_set_timing_enforcement(&dev.ctx, true); /* Wake-up */ iolink_phy_mock_set_wakeup(1); - iolink_process(); + iolink_device_process(&dev.ctx); usleep(200); /* PREOPERATE -> ESTAB_COM */ @@ -137,7 +140,7 @@ static void test_dll_fallback_on_crc_errors(void** state) will_return(mock_phy_recv_byte, ck); will_return(mock_phy_recv_byte, 0); /* No response for Transition Command */ - iolink_process(); + iolink_device_process(&dev.ctx); /* ESTAB_COM -> OPERATE */ uint8_t ok_frame[5] = {0x80, 0x00, 0x00, 0x00, 0x00}; @@ -150,8 +153,8 @@ static void test_dll_fallback_on_crc_errors(void** state) expect_any(mock_phy_send, data); expect_value(mock_phy_send, len, 4); will_return(mock_phy_send, 0); - iolink_process(); - assert_int_equal(iolink_get_state(), IOLINK_DLL_STATE_OPERATE); + iolink_device_process(&dev.ctx); + assert_int_equal(iolink_device_get_state(&dev.ctx), IOLINK_DLL_STATE_OPERATE); /* Inject CRC errors to trigger fallback */ for (int r = 0; r < 3; r++) { @@ -161,17 +164,17 @@ static void test_dll_fallback_on_crc_errors(void** state) will_return(mock_phy_recv_byte, bad_frame[i]); } will_return(mock_phy_recv_byte, 0); - iolink_process(); + iolink_device_process(&dev.ctx); } /* Ensure there is exactly one mock value for the final process cycle check */ /* will_return(mock_phy_recv_byte, 0); // Removed: in SIO mode we don't call recv_byte */ /* Next process call applies fallback */ - iolink_process(); + iolink_device_process(&dev.ctx); - assert_int_equal(iolink_get_state(), IOLINK_DLL_STATE_STARTUP); - assert_int_equal(iolink_get_baudrate(), IOLINK_BAUDRATE_COM1); + assert_int_equal(iolink_device_get_state(&dev.ctx), IOLINK_DLL_STATE_STARTUP); + assert_int_equal(iolink_device_get_baudrate(&dev.ctx), IOLINK_BAUDRATE_COM1); } static void test_dll_reject_transition_in_operate(void** state) @@ -180,10 +183,11 @@ static void test_dll_reject_transition_in_operate(void** state) iolink_config_t config = {.m_seq_type = IOLINK_M_SEQ_TYPE_1_1, .pd_in_len = 1, .pd_out_len = 1}; setup_mock_phy(); will_return(mock_phy_init, 0); - iolink_init(&g_phy_mock, &config); + iolink_test_device_t dev; + iolink_test_device_init(&dev, &config, NULL); - move_to_operate(); - assert_int_equal(iolink_get_state(), IOLINK_DLL_STATE_OPERATE); + move_to_operate_ctx(&dev.ctx); + assert_int_equal(iolink_device_get_state(&dev.ctx), IOLINK_DLL_STATE_OPERATE); /* Master sends 0x0F (Transition) while in OPERATE * Type 1_1 frame for pd_out_len=1 is 5 bytes: MC, CKT, PD, OD, CK */ @@ -197,12 +201,12 @@ static void test_dll_reject_transition_in_operate(void** state) } will_return(mock_phy_recv_byte, 0); - iolink_process(); + iolink_device_process(&dev.ctx); /* Should NOT change state and should increment framing_errors */ - assert_int_equal(iolink_get_state(), IOLINK_DLL_STATE_OPERATE); + assert_int_equal(iolink_device_get_state(&dev.ctx), IOLINK_DLL_STATE_OPERATE); iolink_dll_stats_t stats; - iolink_get_dll_stats(&stats); + iolink_device_get_dll_stats(&dev.ctx, &stats); assert_int_not_equal(stats.framing_errors, 0); } @@ -212,9 +216,10 @@ static void test_dll_reject_invalid_mc_channel(void** state) iolink_config_t config = {.m_seq_type = IOLINK_M_SEQ_TYPE_1_1, .pd_in_len = 1, .pd_out_len = 1}; setup_mock_phy(); will_return(mock_phy_init, 0); - iolink_init(&g_phy_mock, &config); + iolink_test_device_t dev; + iolink_test_device_init(&dev, &config, NULL); - move_to_operate(); + move_to_operate_ctx(&dev.ctx); /* MC with reserved channel bits (e.g., 0x20 | 0x80) */ uint8_t mc = 0xA0; @@ -227,10 +232,10 @@ static void test_dll_reject_invalid_mc_channel(void** state) } will_return(mock_phy_recv_byte, 0); - iolink_process(); + iolink_device_process(&dev.ctx); iolink_dll_stats_t stats; - iolink_get_dll_stats(&stats); + iolink_device_get_dll_stats(&dev.ctx, &stats); assert_int_not_equal(stats.framing_errors, 0); } diff --git a/tests/test_ds.c b/tests/test_ds.c index 4cd1cef..894ede2 100644 --- a/tests/test_ds.c +++ b/tests/test_ds.c @@ -166,6 +166,55 @@ static void test_ds_param_round_trip(void** state) assert_memory_equal(buf, loc, strlen(loc)); } +static void test_ds_bound_params_restore_device_context_only(void** state) +{ + (void) state; + iolink_device_info_ctx_t info_a; + iolink_device_info_ctx_t info_b; + iolink_params_ctx_t params_a; + iolink_params_ctx_t params_b; + iolink_ds_ctx_t ds_a; + iolink_ds_ctx_t ds_b; + static const uint8_t tag_a[] = "CtxA"; + static const uint8_t tag_b[] = "CtxB"; + uint8_t backup[IOLINK_DS_IMAGE_MAX]; + uint8_t out[16] = {0U}; + + iolink_device_info_ctx_init(&info_a, NULL); + iolink_device_info_ctx_init(&info_b, NULL); + iolink_params_ctx_init(¶ms_a, &info_a); + iolink_params_ctx_init(¶ms_b, &info_b); + iolink_ds_init(&ds_a, NULL); + iolink_ds_init(&ds_b, NULL); + iolink_ds_bind_params(&ds_a, ¶ms_a); + iolink_ds_bind_params(&ds_b, ¶ms_b); + + assert_int_equal(iolink_params_ctx_set(¶ms_a, IOLINK_IDX_APPLICATION_TAG, 0U, tag_a, + sizeof(tag_a) - 1U, true), + 0); + assert_int_equal(iolink_params_ctx_set(¶ms_b, IOLINK_IDX_APPLICATION_TAG, 0U, tag_b, + sizeof(tag_b) - 1U, true), + 0); + + assert_true(iolink_ds_build_image(&ds_a) > 0); + assert_true(ds_a.image_len <= sizeof(backup)); + memcpy(backup, ds_a.image, ds_a.image_len); + + iolink_params_ctx_factory_reset(¶ms_a); + assert_int_equal(iolink_ds_apply_image(&ds_a, backup, ds_a.image_len), 0); + + assert_int_equal( + iolink_params_ctx_get(¶ms_a, IOLINK_IDX_APPLICATION_TAG, 0U, out, sizeof(out)), + (int) (sizeof(tag_a) - 1U)); + assert_memory_equal(out, tag_a, sizeof(tag_a) - 1U); + + memset(out, 0, sizeof(out)); + assert_int_equal( + iolink_params_ctx_get(¶ms_b, IOLINK_IDX_APPLICATION_TAG, 0U, out, sizeof(out)), + (int) (sizeof(tag_b) - 1U)); + assert_memory_equal(out, tag_b, sizeof(tag_b) - 1U); +} + static void test_ds_apply_rejects_truncated(void** state) { (void) state; @@ -227,6 +276,7 @@ int main(void) cmocka_unit_test(test_ds_commands_locked), cmocka_unit_test(test_ds_commands_unlocked), cmocka_unit_test(test_ds_param_round_trip), + cmocka_unit_test(test_ds_bound_params_restore_device_context_only), cmocka_unit_test(test_ds_apply_rejects_truncated), cmocka_unit_test(test_ds_download_recovery), }; diff --git a/tests/test_error_recovery.c b/tests/test_error_recovery.c index c45b25d..6248f53 100644 --- a/tests/test_error_recovery.c +++ b/tests/test_error_recovery.c @@ -18,7 +18,7 @@ #include #include -#include "iolinki/iolink.h" +#include "iolinki/device.h" #include "iolinki/events.h" #include "iolinki/dll.h" #include "test_helpers.h" @@ -30,9 +30,10 @@ static void test_crc_error_recovery(void** state) setup_mock_phy(); will_return(mock_phy_init, 0); - iolink_init(&g_phy_mock, &config); + iolink_test_device_t dev; + iolink_test_device_init(&dev, &config, NULL); - move_to_operate(); + move_to_operate_ctx(&dev.ctx); /* 2. Simulate 3 CRC errors (Type 1_1 with 1-byte PD: MC, CKT, PD(1), OD(1), CK = 5 bytes) */ for (int r = 0; r < 3; r++) { @@ -50,11 +51,11 @@ static void test_crc_error_recovery(void** state) will_return(mock_phy_recv_byte, 0); /* End frame */ /* One call to process the whole available frame */ - iolink_process(); + iolink_device_process(&dev.ctx); } /* 3. Check if error was detected (Event 0x5000 triggered after 3 retries) */ - assert_true(iolink_events_pending(iolink_get_events_ctx())); + assert_true(iolink_events_pending(iolink_device_get_events_ctx(&dev.ctx))); } static void test_communication_timeout(void** state) @@ -62,16 +63,17 @@ static void test_communication_timeout(void** state) (void) state; setup_mock_phy(); will_return(mock_phy_init, 0); - iolink_init(&g_phy_mock, NULL); + iolink_test_device_t dev; + iolink_test_device_init(&dev, NULL, NULL); /* Trigger wakeup to move from SIO to SDCI */ iolink_phy_mock_set_wakeup(1); - iolink_process(); + iolink_device_process(&dev.ctx); iolink_phy_mock_set_wakeup(0); /* Ensure no data available in SDCI mode */ will_return(mock_phy_recv_byte, 0); - iolink_process(); + iolink_device_process(&dev.ctx); } int main(void) diff --git a/tests/test_helpers.c b/tests/test_helpers.c index a5ff168..5fedc58 100644 --- a/tests/test_helpers.c +++ b/tests/test_helpers.c @@ -32,23 +32,27 @@ static int g_mock_wakeup = 0; static uint8_t g_mock_cq_state = 0U; static uint32_t g_mock_send_delay_us = 0U; -int mock_phy_init(void) +int mock_phy_init(void* user) { + (void) user; return (int) mock(); } -void mock_phy_set_mode(iolink_phy_mode_t mode) +void mock_phy_set_mode(void* user, iolink_phy_mode_t mode) { + (void) user; check_expected(mode); } -void mock_phy_set_baudrate(iolink_baudrate_t baudrate) +void mock_phy_set_baudrate(void* user, iolink_baudrate_t baudrate) { + (void) user; check_expected(baudrate); } -int mock_phy_send(const uint8_t* data, size_t len) +int mock_phy_send(void* user, const uint8_t* data, size_t len) { + (void) user; check_expected_ptr(data); check_expected(len); if (g_mock_send_delay_us > 0U) { @@ -57,8 +61,9 @@ int mock_phy_send(const uint8_t* data, size_t len) return (int) mock(); } -int mock_phy_recv_byte(uint8_t* byte) +int mock_phy_recv_byte(void* user, uint8_t* byte) { + (void) user; int res = (int) mock(); if (res > 0) { *byte = (uint8_t) mock(); @@ -66,25 +71,30 @@ int mock_phy_recv_byte(uint8_t* byte) return res; } -int mock_phy_detect_wakeup(void) +int mock_phy_detect_wakeup(void* user) { + (void) user; int ret = g_mock_wakeup; g_mock_wakeup = 0; return ret; } -void mock_phy_set_cq_line(uint8_t state) +void mock_phy_set_cq_line(void* user, uint8_t state) { + (void) user; g_mock_cq_state = state; } -const iolink_phy_api_t g_phy_mock = {.init = mock_phy_init, +const iolink_phy_api_t g_phy_mock = {.user = NULL, + .init = mock_phy_init, .set_mode = mock_phy_set_mode, .set_baudrate = mock_phy_set_baudrate, .send = mock_phy_send, .recv_byte = mock_phy_recv_byte, .detect_wakeup = mock_phy_detect_wakeup, - .set_cq_line = mock_phy_set_cq_line}; + .set_cq_line = mock_phy_set_cq_line, + .get_voltage_mv = NULL, + .is_short_circuit = NULL}; void setup_mock_phy(void) { @@ -98,19 +108,39 @@ void setup_mock_phy(void) g_mock_send_delay_us = 0U; } -void move_to_operate(void) +int iolink_test_device_init(iolink_test_device_t* dev, const iolink_config_t* stack, + const iolink_app_callbacks_t* callbacks) { - /* STARTUP -> PREOPERATE (via WakeUp -> AWAITING_COMM) */ + if (dev == NULL) { + return -1; + } - /* 1. Send WakeUp to switch to SDCI */ - g_mock_wakeup = 1; /* iolink_phy_mock_set_wakeup(1) */ - iolink_process(); + (void) memset(dev, 0, sizeof(*dev)); + dev->cfg.phy = g_phy_mock; + if (stack != NULL) { + dev->cfg.stack = *stack; + } + else { + dev->cfg.stack.m_seq_type = IOLINK_M_SEQ_TYPE_0; + dev->cfg.stack.min_cycle_time = 0U; + } + dev->cfg.app_callbacks = callbacks; - /* 2. Wait for T_DWU (assuming timing might be enforced) */ - usleep(200); /* > 54us T_DWU */ + return iolink_device_init(&dev->ctx, &dev->cfg); +} - /* 3. Send Transition Command immediately (AWAITING_COMM handles first byte) - This avoids needing to handle the response from an Idle frame. */ +void move_to_operate_ctx(iolink_device_ctx_t* ctx) +{ + if (ctx == NULL) { + return; + } + + /* STARTUP -> PREOPERATE (via WakeUp -> AWAITING_COMM) */ + g_mock_wakeup = 1; + iolink_device_process(ctx); + + /* Wait for T_DWU (assuming timing might be enforced) */ + usleep(200); /* > 54us T_DWU */ /* PREOPERATE -> ESTAB_COM (on MC=0x0F + Correct CK) */ uint8_t mc = IOLINK_MC_TRANSITION_COMMAND; @@ -121,12 +151,12 @@ void move_to_operate(void) will_return(mock_phy_recv_byte, 1); will_return(mock_phy_recv_byte, ck); will_return(mock_phy_recv_byte, 0); - iolink_process(); + iolink_device_process(ctx); /* ESTAB_COM -> OPERATE (send first valid frame for configured type) */ - iolink_m_seq_type_t type = iolink_get_m_seq_type(); - uint8_t pd_out_len = iolink_get_pd_out_len(); - uint8_t pd_in_len = iolink_get_pd_in_len(); + iolink_m_seq_type_t type = iolink_device_get_m_seq_type(ctx); + uint8_t pd_out_len = iolink_device_get_pd_out_len(ctx); + uint8_t pd_in_len = iolink_device_get_pd_in_len(ctx); uint8_t od_len = ((type == IOLINK_M_SEQ_TYPE_2_1) || (type == IOLINK_M_SEQ_TYPE_2_2) || (type == IOLINK_M_SEQ_TYPE_2_V)) ? 2U @@ -144,7 +174,7 @@ void move_to_operate(void) expect_any(mock_phy_send, data); expect_value(mock_phy_send, len, 2); will_return(mock_phy_send, 0); - iolink_process(); + iolink_device_process(ctx); return; } @@ -166,7 +196,7 @@ void move_to_operate(void) expect_value(mock_phy_send, len, resp_len); will_return(mock_phy_send, 0); - iolink_process(); + iolink_device_process(ctx); } void iolink_phy_mock_reset(void) diff --git a/tests/test_helpers.h b/tests/test_helpers.h index 5a56903..0e6734e 100644 --- a/tests/test_helpers.h +++ b/tests/test_helpers.h @@ -17,6 +17,7 @@ #include #include #include "iolinki/iolink.h" +#include "iolinki/device.h" #include "iolinki/phy.h" #include "iolinki/crc.h" #include "iolinki/protocol.h" @@ -26,13 +27,13 @@ extern uint8_t g_tx_buf[1024]; extern uint8_t g_rx_buf[1024]; /* Mock implementations (exported for CMocka symbols) */ -int mock_phy_init(void); -void mock_phy_set_mode(iolink_phy_mode_t mode); -void mock_phy_set_baudrate(iolink_baudrate_t baudrate); -int mock_phy_send(const uint8_t* data, size_t len); -int mock_phy_recv_byte(uint8_t* byte); -int mock_phy_detect_wakeup(void); -void mock_phy_set_cq_line(uint8_t state); +int mock_phy_init(void* user); +void mock_phy_set_mode(void* user, iolink_phy_mode_t mode); +void mock_phy_set_baudrate(void* user, iolink_baudrate_t baudrate); +int mock_phy_send(void* user, const uint8_t* data, size_t len); +int mock_phy_recv_byte(void* user, uint8_t* byte); +int mock_phy_detect_wakeup(void* user); +void mock_phy_set_cq_line(void* user, uint8_t state); /* Mock PHY driver API */ extern const iolink_phy_api_t g_phy_mock; @@ -40,11 +41,20 @@ extern const iolink_phy_api_t g_phy_mock; /* Helper to setup mock expectations */ void setup_mock_phy(void); void iolink_phy_mock_reset(void); -void move_to_operate(void); void iolink_phy_mock_set_wakeup(int detected); uint8_t iolink_phy_mock_get_cq_state(void); void iolink_phy_mock_set_send_delay_us(uint32_t delay_us); +typedef struct +{ + iolink_device_ctx_t ctx; + iolink_device_config_t cfg; +} iolink_test_device_t; + +int iolink_test_device_init(iolink_test_device_t* dev, const iolink_config_t* stack, + const iolink_app_callbacks_t* callbacks); +void move_to_operate_ctx(iolink_device_ctx_t* ctx); + /* Mock Storage for Data Storage (DS) testing */ #include "iolinki/data_storage.h" extern const iolink_ds_storage_api_t g_ds_storage_mock; diff --git a/tests/test_init.c b/tests/test_init.c index 7cedd3b..75ec58a 100644 --- a/tests/test_init.c +++ b/tests/test_init.c @@ -18,29 +18,34 @@ #include #include -#include "iolinki/iolink.h" +#include "iolinki/device.h" /* Local mocks to avoid linking issues with CMocka symbols */ -static int local_mock_phy_init(void) +static int local_mock_phy_init(void* user) { + (void) user; return (int) mock(); } -static void local_mock_phy_set_mode(iolink_phy_mode_t mode) +static void local_mock_phy_set_mode(void* user, iolink_phy_mode_t mode) { + (void) user; (void) mode; } -static void local_mock_phy_set_baudrate(iolink_baudrate_t baudrate) +static void local_mock_phy_set_baudrate(void* user, iolink_baudrate_t baudrate) { + (void) user; (void) baudrate; } -static int local_mock_phy_send(const uint8_t* data, size_t len) +static int local_mock_phy_send(void* user, const uint8_t* data, size_t len) { + (void) user; (void) data; (void) len; return 0; } -static int local_mock_phy_recv_byte(uint8_t* byte) +static int local_mock_phy_recv_byte(void* user, uint8_t* byte) { + (void) user; (void) byte; return 0; } @@ -51,28 +56,43 @@ static const iolink_phy_api_t local_phy_mock = {.init = local_mock_phy_init, .send = local_mock_phy_send, .recv_byte = local_mock_phy_recv_byte}; +static iolink_device_config_t make_device_config(void) +{ + iolink_device_config_t config; + (void) memset(&config, 0, sizeof(config)); + config.phy = local_phy_mock; + return config; +} + /* --- Tests --- */ static void test_iolink_init_success(void** state) { (void) state; + iolink_device_ctx_t ctx; + iolink_device_config_t config = make_device_config(); + will_return(local_mock_phy_init, 0); - int result = iolink_init(&local_phy_mock, NULL); + int result = iolink_device_init(&ctx, &config); assert_int_equal(result, 0); } static void test_iolink_init_fail_null(void** state) { (void) state; - int result = iolink_init(NULL, NULL); + iolink_device_ctx_t ctx; + int result = iolink_device_init(&ctx, NULL); assert_int_not_equal(result, 0); } static void test_iolink_init_fail_driver(void** state) { (void) state; + iolink_device_ctx_t ctx; + iolink_device_config_t config = make_device_config(); + will_return(local_mock_phy_init, -1); - int result = iolink_init(&local_phy_mock, NULL); + int result = iolink_device_init(&ctx, &config); assert_int_equal(result, -1); } diff --git a/tests/test_integration_full.c b/tests/test_integration_full.c index 18fb485..7b1b93b 100644 --- a/tests/test_integration_full.c +++ b/tests/test_integration_full.c @@ -20,7 +20,7 @@ #include #include -#include "iolinki/iolink.h" +#include "iolinki/device.h" #include "iolinki/dll.h" #include "iolinki/isdu.h" #include "iolinki/events.h" @@ -38,14 +38,15 @@ static void test_full_stack_lifecycle(void** state) /* Initialize stack with mock PHY and mock storage */ setup_mock_phy(); will_return(mock_phy_init, 0); - iolink_init(&g_phy_mock, NULL); - iolink_ds_init(iolink_get_ds_ctx(), &g_ds_storage_mock); + iolink_test_device_t dev; + iolink_test_device_init(&dev, NULL, NULL); + iolink_ds_init(iolink_device_get_ds_ctx(&dev.ctx), &g_ds_storage_mock); /*** STEP 1: STARTUP -> PREOPERATE ***/ /* 1. Inject WakeUp (Transitions SIO -> AWAITING_COMM) */ iolink_phy_mock_set_wakeup(1); - iolink_process(); + iolink_device_process(&dev.ctx); usleep(200); /* Note: We rely on the first byte of STEP 2 (ISDU Read) to transition @@ -66,10 +67,10 @@ static void test_full_stack_lifecycle(void** state) expect_any(mock_phy_send, data); expect_value(mock_phy_send, len, 2); will_return(mock_phy_send, 0); - iolink_process(); + iolink_device_process(&dev.ctx); /*** STEP 3: EVENT TRIGGERING ***/ - iolink_events_ctx_t* evt_ctx = iolink_get_events_ctx(); + iolink_events_ctx_t* evt_ctx = iolink_device_get_events_ctx(&dev.ctx); iolink_event_trigger(evt_ctx, 0x1234, IOLINK_EVENT_TYPE_WARNING); assert_true(iolink_events_pending(evt_ctx)); @@ -86,7 +87,7 @@ static void test_full_stack_lifecycle(void** state) expect_any(mock_phy_send, data); expect_value(mock_phy_send, len, 2); will_return(mock_phy_send, 0); - iolink_process(); + iolink_device_process(&dev.ctx); assert_true(iolink_events_pending(evt_ctx)); } @@ -99,12 +100,13 @@ static void test_full_stack_timing_enforcement(void** state) setup_mock_phy(); will_return(mock_phy_init, 0); - iolink_init(&g_phy_mock, &config); + iolink_test_device_t dev; + iolink_test_device_init(&dev, &config, NULL); - move_to_operate(); + move_to_operate_ctx(&dev.ctx); - iolink_set_timing_enforcement(true); - iolink_set_t_ren_limit_us(100); + iolink_device_set_timing_enforcement(&dev.ctx, true); + iolink_device_set_t_ren_limit_us(&dev.ctx, 100); iolink_phy_mock_set_send_delay_us(500); uint8_t frame[5] = {0x80, 0x00, 0x00, 0x00, 0x00}; @@ -118,7 +120,7 @@ static void test_full_stack_timing_enforcement(void** state) expect_any(mock_phy_send, data); expect_value(mock_phy_send, len, 4); will_return(mock_phy_send, 0); - iolink_process(); + iolink_device_process(&dev.ctx); for (int i = 0; i < 5; i++) { will_return(mock_phy_recv_byte, 1); @@ -128,10 +130,10 @@ static void test_full_stack_timing_enforcement(void** state) expect_any(mock_phy_send, data); expect_value(mock_phy_send, len, 4); will_return(mock_phy_send, 0); - iolink_process(); + iolink_device_process(&dev.ctx); iolink_dll_stats_t stats; - iolink_get_dll_stats(&stats); + iolink_device_get_dll_stats(&dev.ctx, &stats); assert_true(stats.t_ren_violations > 0U); assert_true(stats.t_cycle_violations > 0U); } diff --git a/tests/test_m_sequence_types.c b/tests/test_m_sequence_types.c index 0839870..878011a 100644 --- a/tests/test_m_sequence_types.c +++ b/tests/test_m_sequence_types.c @@ -18,7 +18,7 @@ #include #include -#include "iolinki/iolink.h" +#include "iolinki/device.h" #include "iolinki/dll.h" #include "iolinki/application.h" #include "iolinki/crc.h" @@ -28,15 +28,16 @@ static void test_m_seq_type_1_1(void** state) { (void) state; iolink_config_t config = {.m_seq_type = IOLINK_M_SEQ_TYPE_1_1, .pd_in_len = 2, .pd_out_len = 2}; + iolink_test_device_t dev; setup_mock_phy(); will_return(mock_phy_init, 0); - iolink_init(&g_phy_mock, &config); + assert_int_equal(iolink_test_device_init(&dev, &config, NULL), 0); - move_to_operate(); + move_to_operate_ctx(&dev.ctx); uint8_t input_pd[2] = {0xAA, 0xBB}; - iolink_pd_input_update(input_pd, 2, true); + iolink_device_pd_input_update(&dev.ctx, input_pd, 2, true); /* Type 1_1 with 2-byte PD: Req = MC, CKT, PD(2), OD(1), CK = 6 bytes */ uint8_t frame[] = {0x80, 0x00, 0x11, 0x22, 0x00, 0x00}; @@ -53,7 +54,7 @@ static void test_m_seq_type_1_1(void** state) expect_value(mock_phy_send, len, 5); will_return(mock_phy_send, 0); - iolink_process(); + iolink_device_process(&dev.ctx); } int main(void) diff --git a/tests/test_pd.c b/tests/test_pd.c index 7589a24..31ffb63 100644 --- a/tests/test_pd.c +++ b/tests/test_pd.c @@ -18,7 +18,7 @@ #include #include -#include "iolinki/iolink.h" +#include "iolinki/device.h" #include "iolinki/application.h" #include "iolinki/crc.h" #include "test_helpers.h" @@ -28,17 +28,18 @@ static void test_pd_input_output(void** state) (void) state; iolink_config_t config = {.pd_in_len = 2, .pd_out_len = 2, .m_seq_type = IOLINK_M_SEQ_TYPE_2_2}; + iolink_test_device_t dev; setup_mock_phy(); will_return(mock_phy_init, 0); - iolink_init(&g_phy_mock, &config); + assert_int_equal(iolink_test_device_init(&dev, &config, NULL), 0); /* Move to OPERATE */ - move_to_operate(); + move_to_operate_ctx(&dev.ctx); /* 1. Set Input PD */ uint8_t input[2] = {0x11, 0x22}; - iolink_pd_input_update(input, 2, true); + iolink_device_pd_input_update(&dev.ctx, input, 2, true); /* 2. Simulate Master Frame (Type 2_2: MC, CKT, PD_OUT(2), OD(2), CK) -> 7 bytes */ uint8_t frame[7] = {0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; @@ -55,7 +56,7 @@ static void test_pd_input_output(void** state) expect_value(mock_phy_send, len, 6); will_return(mock_phy_send, 0); - iolink_process(); + iolink_device_process(&dev.ctx); } int main(void) diff --git a/tests/test_pd_variable.c b/tests/test_pd_variable.c index a91f1f7..fd987b7 100644 --- a/tests/test_pd_variable.c +++ b/tests/test_pd_variable.c @@ -18,7 +18,7 @@ #include #include -#include "iolinki/iolink.h" +#include "iolinki/device.h" #include "iolinki/dll.h" #include "iolinki/application.h" #include "iolinki/crc.h" @@ -28,11 +28,12 @@ static void test_pd_variable_lengths(void** state) { (void) state; iolink_config_t config = {.m_seq_type = IOLINK_M_SEQ_TYPE_1_V, .pd_in_len = 8, .pd_out_len = 8}; + iolink_test_device_t dev; setup_mock_phy(); will_return(mock_phy_init, 0); - iolink_init(&g_phy_mock, &config); - move_to_operate(); + assert_int_equal(iolink_test_device_init(&dev, &config, NULL), 0); + move_to_operate_ctx(&dev.ctx); /* Type 1_V: Req = MC, CKT, PD(8), OD(1), CK = 12 bytes. */ uint8_t frame[12]; @@ -51,23 +52,24 @@ static void test_pd_variable_lengths(void** state) expect_value(mock_phy_send, len, 11); will_return(mock_phy_send, 0); - iolink_process(); + iolink_device_process(&dev.ctx); } static void test_pd_invalid_flag(void** state) { (void) state; iolink_config_t config = {.m_seq_type = IOLINK_M_SEQ_TYPE_1_1, .pd_in_len = 1, .pd_out_len = 1}; + iolink_test_device_t dev; setup_mock_phy(); will_return(mock_phy_init, 0); - iolink_init(&g_phy_mock, &config); + assert_int_equal(iolink_test_device_init(&dev, &config, NULL), 0); /* Ensure pd_in_len is 1 for this test to match 4-byte response expectation */ uint8_t dummy = 0; - iolink_pd_input_update(&dummy, 1, false); + iolink_device_pd_input_update(&dev.ctx, &dummy, 1, false); - move_to_operate(); + move_to_operate_ctx(&dev.ctx); /* Type 1_1 with 1-byte PD: Req = MC, CKT, PD(1), OD(1), CK = 5 bytes. Resp: Stat, PD(1), OD(1), CK = 4 bytes. @@ -85,7 +87,7 @@ static void test_pd_invalid_flag(void** state) expect_value(mock_phy_send, len, 4); will_return(mock_phy_send, 0); - iolink_process(); + iolink_device_process(&dev.ctx); } static void test_pd_runtime_negotiation(void** state) @@ -93,33 +95,37 @@ static void test_pd_runtime_negotiation(void** state) (void) state; /* Variable type with a maximum of 8 octets. */ iolink_config_t config = {.m_seq_type = IOLINK_M_SEQ_TYPE_1_V, .pd_in_len = 8, .pd_out_len = 8}; + iolink_test_device_t dev; + setup_mock_phy(); will_return(mock_phy_init, 0); - iolink_init(&g_phy_mock, &config); + assert_int_equal(iolink_test_device_init(&dev, &config, NULL), 0); - assert_int_equal(iolink_get_pd_in_len(), 8); + assert_int_equal(iolink_device_get_pd_in_len(&dev.ctx), 8); /* Shrink the runtime PD lengths; change is observable via the getters. */ - assert_int_equal(iolink_set_pd_length(4, 4), 0); - assert_int_equal(iolink_get_pd_in_len(), 4); - assert_int_equal(iolink_get_pd_out_len(), 4); + assert_int_equal(iolink_device_set_pd_length(&dev.ctx, 4, 4), 0); + assert_int_equal(iolink_device_get_pd_in_len(&dev.ctx), 4); + assert_int_equal(iolink_device_get_pd_out_len(&dev.ctx), 4); /* Exceeding the configured maximum is rejected. */ - assert_int_equal(iolink_set_pd_length(16, 16), -1); - assert_int_equal(iolink_get_pd_in_len(), 4); /* unchanged */ + assert_int_equal(iolink_device_set_pd_length(&dev.ctx, 16, 16), -1); + assert_int_equal(iolink_device_get_pd_in_len(&dev.ctx), 4); /* unchanged */ } static void test_pd_fixed_not_negotiable(void** state) { (void) state; iolink_config_t config = {.m_seq_type = IOLINK_M_SEQ_TYPE_1_1, .pd_in_len = 2, .pd_out_len = 2}; + iolink_test_device_t dev; + setup_mock_phy(); will_return(mock_phy_init, 0); - iolink_init(&g_phy_mock, &config); + assert_int_equal(iolink_test_device_init(&dev, &config, NULL), 0); /* Fixed-length M-sequences reject runtime PD-length changes. */ - assert_int_equal(iolink_set_pd_length(1, 1), -2); - assert_int_equal(iolink_get_pd_in_len(), 2); + assert_int_equal(iolink_device_set_pd_length(&dev.ctx, 1, 1), -2); + assert_int_equal(iolink_device_get_pd_in_len(&dev.ctx), 2); } int main(void) diff --git a/tests/test_phy_diagnostics.c b/tests/test_phy_diagnostics.c index a8d9683..773ddae 100644 --- a/tests/test_phy_diagnostics.c +++ b/tests/test_phy_diagnostics.c @@ -26,41 +26,48 @@ static int mock_voltage_mv = 24000; /* Default: 24V (normal) */ static bool mock_short_circuit = false; -static int mock_init(void) +static int mock_init(void* user) { + (void) user; return 0; } -static void mock_set_mode(iolink_phy_mode_t mode) +static void mock_set_mode(void* user, iolink_phy_mode_t mode) { + (void) user; (void) mode; } -static void mock_set_baudrate(iolink_baudrate_t baudrate) +static void mock_set_baudrate(void* user, iolink_baudrate_t baudrate) { + (void) user; (void) baudrate; } -static int mock_send(const uint8_t* data, size_t len) +static int mock_send(void* user, const uint8_t* data, size_t len) { + (void) user; (void) data; (void) len; return (int) len; } -static int mock_recv_byte(uint8_t* byte) +static int mock_recv_byte(void* user, uint8_t* byte) { + (void) user; (void) byte; return 0; /* No data */ } -static int mock_get_voltage_mv(void) +static int mock_get_voltage_mv(void* user) { + (void) user; return mock_voltage_mv; } -static bool mock_is_short_circuit(void) +static bool mock_is_short_circuit(void* user) { + (void) user; return mock_short_circuit; } diff --git a/tests/test_reentrant_device.c b/tests/test_reentrant_device.c new file mode 100644 index 0000000..4c4e27b --- /dev/null +++ b/tests/test_reentrant_device.c @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2026 Andrii Shylenko + * SPDX-License-Identifier: GPL-3.0-or-later + * + * This file is part of iolinki. + * See LICENSE for details. + */ + +#include +#include +#include +#include +#include + +#include "iolinki/device.h" +#include "iolinki/protocol.h" + +static int noop_user(void* user) +{ + (void) user; + return 0; +} + +static void test_device_context_api_initializes_two_configs(void** state) +{ + (void) state; + iolink_device_ctx_t dev_a; + iolink_device_ctx_t dev_b; + static iolink_device_phy_t phy = {.init = noop_user}; + iolink_device_config_t cfg_a = { + .phy = phy, + .stack = + { + .m_seq_type = IOLINK_M_SEQ_TYPE_1_1, + .min_cycle_time = 10U, + .pd_in_len = 1U, + .pd_out_len = 0U, + .t_pd_us = 0U, + }, + }; + iolink_device_config_t cfg_b = { + .phy = phy, + .stack = + { + .m_seq_type = IOLINK_M_SEQ_TYPE_2_1, + .min_cycle_time = 10U, + .pd_in_len = 2U, + .pd_out_len = 2U, + .t_pd_us = 0U, + }, + }; + + assert_true(iolink_device_ctx_size() == sizeof(iolink_device_ctx_t)); + assert_int_equal(iolink_device_init(&dev_a, &cfg_a), 0); + assert_int_equal(iolink_device_init(&dev_b, &cfg_b), 0); + assert_int_equal(iolink_device_get_pd_in_len(&dev_a), 1U); + assert_int_equal(iolink_device_get_pd_out_len(&dev_a), 0U); + assert_int_equal(iolink_device_get_pd_in_len(&dev_b), 2U); + assert_int_equal(iolink_device_get_pd_out_len(&dev_b), 2U); +} + +static void test_parameter_contexts_keep_writable_tags_isolated(void** state) +{ + (void) state; + iolink_device_info_ctx_t info_a; + iolink_device_info_ctx_t info_b; + iolink_params_ctx_t params_a; + iolink_params_ctx_t params_b; + const uint8_t tag_a[] = "DeviceA"; + const uint8_t tag_b[] = "DeviceB"; + uint8_t out_a[32] = {0}; + uint8_t out_b[32] = {0}; + + iolink_device_info_ctx_init(&info_a, NULL); + iolink_device_info_ctx_init(&info_b, NULL); + iolink_params_ctx_init(¶ms_a, &info_a); + iolink_params_ctx_init(¶ms_b, &info_b); + + assert_int_equal(iolink_params_ctx_set(¶ms_a, IOLINK_IDX_APPLICATION_TAG, 0U, tag_a, + sizeof(tag_a) - 1U, true), + 0); + assert_int_equal(iolink_params_ctx_set(¶ms_b, IOLINK_IDX_APPLICATION_TAG, 0U, tag_b, + sizeof(tag_b) - 1U, true), + 0); + assert_int_equal( + iolink_params_ctx_get(¶ms_a, IOLINK_IDX_APPLICATION_TAG, 0U, out_a, sizeof(out_a)), + (int) (sizeof(tag_a) - 1U)); + assert_int_equal( + iolink_params_ctx_get(¶ms_b, IOLINK_IDX_APPLICATION_TAG, 0U, out_b, sizeof(out_b)), + (int) (sizeof(tag_b) - 1U)); + assert_memory_equal(out_a, tag_a, sizeof(tag_a) - 1U); + assert_memory_equal(out_b, tag_b, sizeof(tag_b) - 1U); +} + +int main(void) +{ + const struct CMUnitTest tests[] = { + cmocka_unit_test(test_device_context_api_initializes_two_configs), + cmocka_unit_test(test_parameter_contexts_keep_writable_tags_isolated), + }; + return cmocka_run_group_tests(tests, NULL, NULL); +} diff --git a/tests/test_sio_fallback.c b/tests/test_sio_fallback.c index 55def7b..1b5aba2 100644 --- a/tests/test_sio_fallback.c +++ b/tests/test_sio_fallback.c @@ -15,7 +15,7 @@ #include #include -#include "iolinki/iolink.h" +#include "iolinki/device.h" #include "iolinki/dll.h" #include "iolinki/phy.h" #include "iolinki/crc.h" @@ -28,16 +28,17 @@ static void test_sio_fallback_on_repeated_errors(void** state) iolink_config_t config = {.pd_in_len = 0, .pd_out_len = 0, .m_seq_type = IOLINK_M_SEQ_TYPE_0}; setup_mock_phy(); will_return(mock_phy_init, 0); - iolink_init(&g_phy_mock, &config); + iolink_test_device_t dev; + iolink_test_device_init(&dev, &config, NULL); /* Verify we're in SIO mode initially (default new behavior) */ - assert_int_equal(iolink_get_phy_mode(), IOLINK_PHY_MODE_SIO); + assert_int_equal(iolink_device_get_phy_mode(&dev.ctx), IOLINK_PHY_MODE_SIO); /* Move to OPERATE */ - move_to_operate(); + move_to_operate_ctx(&dev.ctx); /* Verify we're in SDCI mode */ - assert_int_equal(iolink_get_phy_mode(), IOLINK_PHY_MODE_SDCI); + assert_int_equal(iolink_device_get_phy_mode(&dev.ctx), IOLINK_PHY_MODE_SDCI); /* Inject CRC errors to trigger fallback threshold (3 is the stack's threshold) */ for (int i = 0; i < 3; i++) { @@ -50,11 +51,11 @@ static void test_sio_fallback_on_repeated_errors(void** state) will_return(mock_phy_recv_byte, bad_frame[1]); will_return(mock_phy_recv_byte, 0); - iolink_process(); + iolink_device_process(&dev.ctx); } /* After 3 fallbacks, should be in SIO mode */ - assert_int_equal(iolink_get_phy_mode(), IOLINK_PHY_MODE_SIO); + assert_int_equal(iolink_device_get_phy_mode(&dev.ctx), IOLINK_PHY_MODE_SIO); } static void test_sio_recovery_on_stable_communication(void** state) @@ -64,10 +65,11 @@ static void test_sio_recovery_on_stable_communication(void** state) iolink_config_t config = {.pd_in_len = 0, .pd_out_len = 0, .m_seq_type = IOLINK_M_SEQ_TYPE_0}; setup_mock_phy(); will_return(mock_phy_init, 0); - iolink_init(&g_phy_mock, &config); + iolink_test_device_t dev; + iolink_test_device_init(&dev, &config, NULL); /* Move to OPERATE */ - move_to_operate(); + move_to_operate_ctx(&dev.ctx); /* Trigger SIO fallback by injecting errors (3 is the threshold) */ for (int i = 0; i < 3; i++) { @@ -77,16 +79,16 @@ static void test_sio_recovery_on_stable_communication(void** state) will_return(mock_phy_recv_byte, 1); will_return(mock_phy_recv_byte, bad_frame[1]); will_return(mock_phy_recv_byte, 0); - iolink_process(); + iolink_device_process(&dev.ctx); } - assert_int_equal(iolink_get_phy_mode(), IOLINK_PHY_MODE_SIO); + assert_int_equal(iolink_device_get_phy_mode(&dev.ctx), IOLINK_PHY_MODE_SIO); /* Now send valid frames to recover */ /* 1. WakeUp (SIO -> AWAITING_COMM) */ iolink_phy_mock_set_wakeup(1); - iolink_process(); + iolink_device_process(&dev.ctx); usleep(200); /* 2. Transition (AWAITING_COMM handles first byte) */ @@ -99,7 +101,7 @@ static void test_sio_recovery_on_stable_communication(void** state) will_return(mock_phy_recv_byte, ck); will_return(mock_phy_recv_byte, 0); - iolink_process(); + iolink_device_process(&dev.ctx); /* 3. Send valid OPERATE frame */ uint8_t idle_mc = 0x00; @@ -113,10 +115,10 @@ static void test_sio_recovery_on_stable_communication(void** state) expect_any(mock_phy_send, data); expect_value(mock_phy_send, len, 2); will_return(mock_phy_send, 0); - iolink_process(); + iolink_device_process(&dev.ctx); /* Should recover back to SDCI */ - assert_int_equal(iolink_get_phy_mode(), IOLINK_PHY_MODE_SDCI); + assert_int_equal(iolink_device_get_phy_mode(&dev.ctx), IOLINK_PHY_MODE_SDCI); } int main(void) diff --git a/tests/test_timing.c b/tests/test_timing.c index e16cb7a..56e1ba4 100644 --- a/tests/test_timing.c +++ b/tests/test_timing.c @@ -20,7 +20,7 @@ #include #include "iolinki/crc.h" -#include "iolinki/iolink.h" +#include "iolinki/device.h" #include "iolinki/time_utils.h" #include "test_helpers.h" @@ -58,13 +58,14 @@ static void test_t_cycle_violation(void** state) setup_mock_phy(); will_return(mock_phy_init, 0); - iolink_init(&g_phy_mock, &config); + iolink_test_device_t dev; + iolink_test_device_init(&dev, &config, NULL); /* Move to OPERATE without timing enforcement */ - move_to_operate(); + move_to_operate_ctx(&dev.ctx); /* Enable timing enforcement */ - iolink_set_timing_enforcement(true); + iolink_device_set_timing_enforcement(&dev.ctx, true); /* Send two back-to-back valid frames (Type 1_1) */ uint8_t frame[5] = {0x80, 0x00, 0x00, 0x00, 0x00}; @@ -78,7 +79,7 @@ static void test_t_cycle_violation(void** state) expect_any(mock_phy_send, data); expect_value(mock_phy_send, len, 4); will_return(mock_phy_send, 0); - iolink_process(); + iolink_device_process(&dev.ctx); for (int i = 0; i < 5; i++) { will_return(mock_phy_recv_byte, 1); @@ -88,10 +89,10 @@ static void test_t_cycle_violation(void** state) expect_any(mock_phy_send, data); expect_value(mock_phy_send, len, 4); will_return(mock_phy_send, 0); - iolink_process(); + iolink_device_process(&dev.ctx); iolink_dll_stats_t stats; - iolink_get_dll_stats(&stats); + iolink_device_get_dll_stats(&dev.ctx, &stats); assert_true(stats.t_cycle_violations > 0U); } @@ -102,9 +103,10 @@ static void test_t_ren_violation(void** state) setup_mock_phy(); will_return(mock_phy_init, 0); - iolink_init(&g_phy_mock, &config); - move_to_operate(); - iolink_set_timing_enforcement(true); + iolink_test_device_t dev; + iolink_test_device_init(&dev, &config, NULL); + move_to_operate_ctx(&dev.ctx); + iolink_device_set_timing_enforcement(&dev.ctx, true); /* Send a valid frame, but mock PHY send will be too slow? Actually t_ren is checked against DLL processing time. */ @@ -124,9 +126,9 @@ static void test_t_ren_violation(void** state) /* We need to trick the time. Since we use real time, we just wait a bit in a mock? But DLL calls send() immediately after data collect. - To trigger t_ren violation, we'd need iolink_process to take long. + To trigger t_ren violation, we'd need device processing to take long. */ - iolink_process(); + iolink_device_process(&dev.ctx); /* No violation expected in normal run. Testing timing enforcement is hard with real system clock in unit tests. @@ -141,7 +143,8 @@ static void test_t_pd_delay(void** state) setup_mock_phy(); will_return(mock_phy_init, 0); - iolink_init(&g_phy_mock, &config); + iolink_test_device_t dev; + iolink_test_device_init(&dev, &config, NULL); /* Send a valid Type 0 frame before t_pd expires; expect no response */ uint8_t mc = 0x00; @@ -151,10 +154,10 @@ static void test_t_pd_delay(void** state) will_return(mock_phy_recv_byte, 1); will_return(mock_phy_recv_byte, ck); will_return(mock_phy_recv_byte, 0); - iolink_process(); + iolink_device_process(&dev.ctx); iolink_dll_stats_t stats; - iolink_get_dll_stats(&stats); + iolink_device_get_dll_stats(&dev.ctx, &stats); assert_true(stats.timing_errors > 0U); assert_true(stats.t_pd_violations > 0U); @@ -163,7 +166,7 @@ static void test_t_pd_delay(void** state) /* Trigger WakeUp to get out of STARTUP */ iolink_phy_mock_set_wakeup(1); - iolink_process(); + iolink_device_process(&dev.ctx); iolink_phy_mock_set_wakeup(0); /* Move to PREOPERATE state (AWAITING_COMM handles first byte) */ @@ -177,7 +180,7 @@ static void test_t_pd_delay(void** state) expect_any(mock_phy_send, data); expect_value(mock_phy_send, len, 2); will_return(mock_phy_send, 0); - iolink_process(); + iolink_device_process(&dev.ctx); /* In PREOPERATE, send Transition Command (0x0F) - no response expected */ uint8_t trans_mc = 0x0F; @@ -187,7 +190,7 @@ static void test_t_pd_delay(void** state) will_return(mock_phy_recv_byte, 1); will_return(mock_phy_recv_byte, trans_ck); will_return(mock_phy_recv_byte, 0); - iolink_process(); + iolink_device_process(&dev.ctx); } static void test_t_byte_violation(void** state) @@ -197,9 +200,10 @@ static void test_t_byte_violation(void** state) setup_mock_phy(); will_return(mock_phy_init, 0); - iolink_init(&g_phy_mock, &config); - move_to_operate(); - iolink_set_timing_enforcement(true); + iolink_test_device_t dev; + iolink_test_device_init(&dev, &config, NULL); + move_to_operate_ctx(&dev.ctx); + iolink_device_set_timing_enforcement(&dev.ctx, true); /* Mock a slow byte reception (t_byte violation) */ /* Master sends 5 bytes for Type 1_1. We send 2 and then timeout. */ @@ -216,15 +220,15 @@ static void test_t_byte_violation(void** state) /* Byte 3 -> Timeout */ will_return(mock_phy_recv_byte, 0); - iolink_process(); + iolink_device_process(&dev.ctx); /* Now wait for t_byte_limit and call process again to trigger silence detection */ usleep(5000); /* COM2 t_byte limit is ~416us, 5ms is plenty */ will_return(mock_phy_recv_byte, 0); - iolink_process(); + iolink_device_process(&dev.ctx); iolink_dll_stats_t stats; - iolink_get_dll_stats(&stats); + iolink_device_get_dll_stats(&dev.ctx, &stats); /* Should have 1 timing error (t_byte) */ assert_true(stats.t_byte_violations > 0U); }