#include "serial_service.h" #include #include #include "board_pins.h" #include "driver/gpio.h" #include "driver/uart.h" #include "esp_check.h" #include "esp_heap_caps.h" #include "esp_log.h" #include "freertos/queue.h" #include "freertos/semphr.h" #include "freertos/stream_buffer.h" #include "freertos/task.h" #include "rs232_hw_test.h" #include "rs232_port_owner.h" #define SERIAL_UART_RX_RING_SIZE 8192 #define SERIAL_UART_EVENT_QUEUE_SIZE 64 #define SERIAL_RX_STREAM_SIZE 16384U #define SERIAL_TX_STREAM_SIZE 8192U #define SERIAL_RX_STREAM_STORAGE_SIZE (SERIAL_RX_STREAM_SIZE + 1U) #define SERIAL_TX_STREAM_STORAGE_SIZE (SERIAL_TX_STREAM_SIZE + 1U) #define SERIAL_IO_CHUNK_SIZE 256 #define SERIAL_TASK_STACK_SIZE 4096 #define SERIAL_TASK_PRIORITY 10 #define SERIAL_TASK_IDLE_POLL_MS 50 #define SERIAL_TASK_TX_POLL_MS 10 #define SERIAL_STOP_TIMEOUT_MS 1000 static const char *TAG = "serial_service"; static SemaphoreHandle_t s_state_mutex; static SemaphoreHandle_t s_task_stopped; static StreamBufferHandle_t s_rx_stream; static StreamBufferHandle_t s_tx_stream; static StaticStreamBuffer_t s_rx_stream_control; static StaticStreamBuffer_t s_tx_stream_control; static uint8_t *s_rx_stream_storage; static uint8_t *s_tx_stream_storage; static QueueHandle_t s_uart_event_queue; static TaskHandle_t s_event_task; static portMUX_TYPE s_counter_lock = portMUX_INITIALIZER_UNLOCKED; static serial_config_t s_config; static serial_modem_state_t s_modem_state; static serial_service_counters_t s_counters; static bool s_initialized; static atomic_bool s_running; static atomic_bool s_stop_requested; static atomic_size_t s_tx_task_pending; static bool s_session_active; static bool s_static_mode_safe; static TickType_t milliseconds_to_ticks(uint32_t milliseconds) { TickType_t ticks = pdMS_TO_TICKS(milliseconds); return (milliseconds > 0 && ticks == 0) ? 1 : ticks; } static void add_counter(uint64_t *counter, uint64_t amount) { taskENTER_CRITICAL(&s_counter_lock); *counter += amount; taskEXIT_CRITICAL(&s_counter_lock); } static serial_modem_state_t read_modem_state(void) { return (serial_modem_state_t) { /* MAX3243 receiver outputs are low when modem-control inputs assert. */ .dcd = gpio_get_level(RS232_DCD_GPIO) == 0, .dsr = gpio_get_level(RS232_DSR_GPIO) == 0, .cts = gpio_get_level(RS232_CTS_GPIO) == 0, .ri = gpio_get_level(RS232_RI_GPIO) == 0, .valid = gpio_get_level(RS232_VALID_GPIO) != 0, }; } static void poll_modem_state(void) { serial_modem_state_t current = read_modem_state(); taskENTER_CRITICAL(&s_counter_lock); if (current.dcd != s_modem_state.dcd) { ++s_counters.dcd_transitions; } if (current.dsr != s_modem_state.dsr) { ++s_counters.dsr_transitions; } if (current.cts != s_modem_state.cts) { ++s_counters.cts_transitions; } if (current.ri != s_modem_state.ri) { ++s_counters.ri_transitions; } if (current.valid != s_modem_state.valid) { ++s_counters.valid_transitions; } s_modem_state = current; taskEXIT_CRITICAL(&s_counter_lock); } static bool configured_dtr_active(void) { switch (s_config.dtr_behavior) { case SERIAL_CONFIG_DTR_ACTIVE: return true; case SERIAL_CONFIG_DTR_ON_CONNECT: return s_session_active; case SERIAL_CONFIG_DTR_INACTIVE: default: return false; } } static esp_err_t prepare_gpio_for_uart(void) { s_static_mode_safe = false; /* Keep every RS-232 driver disabled while GPIO-matrix routing changes. */ ESP_RETURN_ON_ERROR(gpio_set_level(RS232_FORCE_OFF_N_GPIO, 0), TAG, "Shut down MAX3243"); const gpio_config_t shutdown_config = { .pin_bit_mask = 1ULL << RS232_FORCE_OFF_N_GPIO, .mode = GPIO_MODE_INPUT_OUTPUT_OD, .pull_up_en = GPIO_PULLUP_ENABLE, .pull_down_en = GPIO_PULLDOWN_DISABLE, .intr_type = GPIO_INTR_DISABLE, }; ESP_RETURN_ON_ERROR(gpio_config(&shutdown_config), TAG, "Configure OFF GPIO"); /* * TX and RTS start in their inactive logic-1 state. DTR follows policy, * where logic 0 is the asserted RS-232 modem-control state. */ ESP_RETURN_ON_ERROR(gpio_set_level(RS232_TX_GPIO, 1), TAG, "Set TX idle latch"); ESP_RETURN_ON_ERROR(gpio_set_level(RS232_RTS_GPIO, 1), TAG, "Set RTS idle latch"); ESP_RETURN_ON_ERROR( gpio_set_level(RS232_DTR_GPIO, configured_dtr_active() ? 0 : 1), TAG, "Set DTR policy latch"); const gpio_config_t output_config = { .pin_bit_mask = (1ULL << RS232_TX_GPIO) | (1ULL << RS232_RTS_GPIO) | (1ULL << RS232_DTR_GPIO), .mode = GPIO_MODE_OUTPUT, .pull_up_en = GPIO_PULLUP_DISABLE, .pull_down_en = GPIO_PULLDOWN_DISABLE, .intr_type = GPIO_INTR_DISABLE, }; ESP_RETURN_ON_ERROR(gpio_config(&output_config), TAG, "Configure RS-232 outputs"); const gpio_config_t input_config = { .pin_bit_mask = (1ULL << RS232_RX_GPIO) | (1ULL << RS232_CTS_GPIO) | (1ULL << RS232_DSR_GPIO) | (1ULL << RS232_DCD_GPIO) | (1ULL << RS232_RI_GPIO) | (1ULL << RS232_VALID_GPIO), .mode = GPIO_MODE_INPUT, .pull_up_en = GPIO_PULLUP_DISABLE, .pull_down_en = GPIO_PULLDOWN_DISABLE, .intr_type = GPIO_INTR_DISABLE, }; return gpio_config(&input_config); } static void enqueue_received_data(const uint8_t *data, size_t size) { size_t accepted = xStreamBufferSend(s_rx_stream, data, size, 0); add_counter(&s_counters.rx_bytes, size); if (accepted < size) { add_counter(&s_counters.rx_dropped_bytes, size - accepted); } } static void drain_uart_receive_ring(size_t suggested_size) { uint8_t buffer[SERIAL_IO_CHUNK_SIZE]; size_t remaining = suggested_size; while (!s_stop_requested) { size_t request = sizeof(buffer); if (remaining > 0 && remaining < request) { request = remaining; } int count = uart_read_bytes(RS232_UART_PORT, buffer, request, 0); if (count <= 0) { break; } enqueue_received_data(buffer, (size_t)count); if (remaining > 0) { if ((size_t)count >= remaining) { remaining = 0; } else { remaining -= (size_t)count; } } /* For buffer-full recovery, continue until ring and stashed data are drained. */ if (suggested_size > 0 && remaining == 0) { break; } } } static void handle_uart_event(const uart_event_t *event) { switch (event->type) { case UART_DATA: drain_uart_receive_ring(event->size); break; case UART_BUFFER_FULL: add_counter(&s_counters.buffer_full_events, 1); drain_uart_receive_ring(0); break; case UART_FIFO_OVF: add_counter(&s_counters.fifo_overflows, 1); uart_flush_input(RS232_UART_PORT); xQueueReset(s_uart_event_queue); break; case UART_FRAME_ERR: add_counter(&s_counters.frame_errors, 1); break; case UART_PARITY_ERR: add_counter(&s_counters.parity_errors, 1); break; case UART_BREAK: case UART_DATA_BREAK: add_counter(&s_counters.breaks, 1); break; default: break; } } static void serial_event_task(void *context) { (void)context; uint8_t pending[SERIAL_IO_CHUNK_SIZE]; size_t pending_size = 0; size_t pending_offset = 0; while (!s_stop_requested) { if (pending_offset == pending_size) { pending_size = xStreamBufferReceive(s_tx_stream, pending, sizeof(pending), 0); pending_offset = 0; s_tx_task_pending = pending_size; } if (pending_offset < pending_size) { /* * uart_tx_chars is nonblocking and therefore remains safe when CTS * is deasserted indefinitely. Unsent bytes stay in this task's * local pending buffer until hardware FIFO space is available. */ int sent = uart_tx_chars( RS232_UART_PORT, (const char *)(pending + pending_offset), pending_size - pending_offset); if (sent > 0) { pending_offset += (size_t)sent; s_tx_task_pending = pending_size - pending_offset; add_counter(&s_counters.tx_sent_to_uart_bytes, (uint64_t)sent); } } TickType_t event_wait = milliseconds_to_ticks( pending_offset < pending_size || xStreamBufferBytesAvailable(s_tx_stream) > 0 ? SERIAL_TASK_TX_POLL_MS : SERIAL_TASK_IDLE_POLL_MS); uart_event_t event; if (xQueueReceive(s_uart_event_queue, &event, event_wait) == pdTRUE) { handle_uart_event(&event); } /* Event-queue notifications can be dropped; the ring length is authoritative. */ drain_uart_receive_ring(0); poll_modem_state(); } size_t discarded = (pending_size - pending_offset) + xStreamBufferBytesAvailable(s_tx_stream); if (discarded > 0) { add_counter(&s_counters.tx_dropped_bytes, discarded); } s_tx_task_pending = 0; s_event_task = NULL; xSemaphoreGive(s_task_stopped); vTaskDelete(NULL); } static esp_err_t restore_static_mode_or_fault(void) { esp_err_t result = rs232_hw_test_init(); s_static_mode_safe = result == ESP_OK; if (result != ESP_OK) { gpio_set_level(RS232_FORCE_OFF_N_GPIO, 0); rs232_port_mark_fault(RS232_PORT_OWNER_SERVICE); ESP_LOGE(TAG, "Static GPIO restoration failed; MAX3243 disabled and port faulted"); } return result; } static esp_err_t cleanup_failed_start( bool driver_installed, bool restore_static_mode) { esp_err_t result = gpio_set_level(RS232_FORCE_OFF_N_GPIO, 0); if (driver_installed) { esp_err_t delete_error = uart_driver_delete(RS232_UART_PORT); if (delete_error != ESP_OK) { s_running = false; s_stop_requested = true; rs232_port_mark_fault(RS232_PORT_OWNER_SERVICE); ESP_LOGE(TAG, "Could not delete UART1 after failed start; port faulted"); return delete_error; } } s_uart_event_queue = NULL; s_running = false; s_stop_requested = false; if (restore_static_mode) { esp_err_t restore_error = restore_static_mode_or_fault(); if (result == ESP_OK) { result = restore_error; } } return result; } static esp_err_t start_locked(bool restore_static_on_failure) { if (s_running) { return ESP_ERR_INVALID_STATE; } uart_config_t uart_config; ESP_RETURN_ON_ERROR(serial_config_to_uart_config(&s_config, &uart_config), TAG, "Convert serial config"); bool driver_installed = false; esp_err_t err = prepare_gpio_for_uart(); if (err == ESP_OK) { err = uart_driver_install( RS232_UART_PORT, SERIAL_UART_RX_RING_SIZE, 0, SERIAL_UART_EVENT_QUEUE_SIZE, &s_uart_event_queue, 0); driver_installed = err == ESP_OK; } if (err == ESP_OK) { err = uart_param_config(RS232_UART_PORT, &uart_config); } if (err == ESP_OK) { err = uart_set_line_inverse(RS232_UART_PORT, 0); } bool hardware_flow = s_config.flow_control == SERIAL_CONFIG_FLOW_CONTROL_RTS_CTS; if (err == ESP_OK) { err = uart_set_pin( RS232_UART_PORT, RS232_TX_GPIO, RS232_RX_GPIO, hardware_flow ? RS232_RTS_GPIO : UART_PIN_NO_CHANGE, hardware_flow ? RS232_CTS_GPIO : UART_PIN_NO_CHANGE); } if (err == ESP_OK) { err = uart_set_rx_full_threshold(RS232_UART_PORT, 64); } if (err != ESP_OK) { esp_err_t cleanup_error = cleanup_failed_start(driver_installed, restore_static_on_failure); return cleanup_error == ESP_OK ? err : cleanup_error; } if (xStreamBufferReset(s_rx_stream) != pdPASS || xStreamBufferReset(s_tx_stream) != pdPASS) { cleanup_failed_start(driver_installed, restore_static_on_failure); return ESP_ERR_INVALID_STATE; } xSemaphoreTake(s_task_stopped, 0); s_stop_requested = false; s_tx_task_pending = 0; err = gpio_set_level(RS232_FORCE_OFF_N_GPIO, 1); if (err != ESP_OK) { esp_err_t cleanup_error = cleanup_failed_start(driver_installed, restore_static_on_failure); return cleanup_error == ESP_OK ? err : cleanup_error; } vTaskDelay(pdMS_TO_TICKS(20)); serial_modem_state_t initial_modem_state = read_modem_state(); taskENTER_CRITICAL(&s_counter_lock); s_modem_state = initial_modem_state; taskEXIT_CRITICAL(&s_counter_lock); if (xTaskCreate( serial_event_task, "serial_uart", SERIAL_TASK_STACK_SIZE, NULL, SERIAL_TASK_PRIORITY, &s_event_task) != pdPASS) { esp_err_t cleanup_error = cleanup_failed_start(driver_installed, restore_static_on_failure); return cleanup_error == ESP_OK ? ESP_ERR_NO_MEM : cleanup_error; } s_running = true; ESP_LOGI( TAG, "UART1 started: baud=%lu, data-bits=%s, parity=%s, stop-bits=%s, flow=%s, DTR=%s", (unsigned long)s_config.baud_rate, serial_config_data_bits_to_string(s_config.data_bits), serial_config_parity_to_string(s_config.parity), serial_config_stop_bits_to_string(s_config.stop_bits), serial_config_flow_control_to_string(s_config.flow_control), serial_config_dtr_behavior_to_string(s_config.dtr_behavior)); return ESP_OK; } static esp_err_t stop_locked(bool restore_static_mode) { if (!s_running) { return ESP_OK; } s_stop_requested = true; if (xSemaphoreTake(s_task_stopped, pdMS_TO_TICKS(SERIAL_STOP_TIMEOUT_MS)) != pdTRUE) { ESP_LOGE(TAG, "UART service task did not quiesce within %d ms", SERIAL_STOP_TIMEOUT_MS); return ESP_ERR_TIMEOUT; } size_t unread_rx = xStreamBufferBytesAvailable(s_rx_stream); if (unread_rx > 0) { add_counter(&s_counters.rx_dropped_bytes, unread_rx); } esp_err_t result = gpio_set_level(RS232_FORCE_OFF_N_GPIO, 0); esp_err_t delete_error = uart_driver_delete(RS232_UART_PORT); if (delete_error != ESP_OK) { s_running = false; rs232_port_mark_fault(RS232_PORT_OWNER_SERVICE); ESP_LOGE(TAG, "Could not delete UART1; MAX3243 remains disabled and port is faulted"); return delete_error; } s_uart_event_queue = NULL; s_running = false; s_stop_requested = false; if (xStreamBufferReset(s_rx_stream) != pdPASS || xStreamBufferReset(s_tx_stream) != pdPASS) { rs232_port_mark_fault(RS232_PORT_OWNER_SERVICE); return ESP_ERR_INVALID_STATE; } if (restore_static_mode) { esp_err_t restore_error = restore_static_mode_or_fault(); if (result == ESP_OK) { result = restore_error; } } ESP_LOGI(TAG, "UART1 stopped%s", restore_static_mode ? "; GPIOs restored to static idle mode" : " for reconfiguration"); return result; } esp_err_t serial_service_init(const serial_config_t *initial_config) { if (s_initialized) { return ESP_ERR_INVALID_STATE; } ESP_RETURN_ON_ERROR(serial_config_validate(initial_config), TAG, "Validate initial config"); s_state_mutex = xSemaphoreCreateMutex(); s_task_stopped = xSemaphoreCreateBinary(); s_rx_stream_storage = heap_caps_calloc_prefer( 1U, SERIAL_RX_STREAM_STORAGE_SIZE, 2, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); s_tx_stream_storage = heap_caps_calloc_prefer( 1U, SERIAL_TX_STREAM_STORAGE_SIZE, 2, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); if (s_rx_stream_storage != NULL) { s_rx_stream = xStreamBufferCreateStatic( SERIAL_RX_STREAM_STORAGE_SIZE, 1U, s_rx_stream_storage, &s_rx_stream_control); } if (s_tx_stream_storage != NULL) { s_tx_stream = xStreamBufferCreateStatic( SERIAL_TX_STREAM_STORAGE_SIZE, 1U, s_tx_stream_storage, &s_tx_stream_control); } if (s_state_mutex == NULL || s_task_stopped == NULL || s_rx_stream == NULL || s_tx_stream == NULL) { if (s_state_mutex != NULL) { vSemaphoreDelete(s_state_mutex); } if (s_task_stopped != NULL) { vSemaphoreDelete(s_task_stopped); } if (s_rx_stream != NULL) { vStreamBufferDelete(s_rx_stream); } if (s_tx_stream != NULL) { vStreamBufferDelete(s_tx_stream); } heap_caps_free(s_rx_stream_storage); heap_caps_free(s_tx_stream_storage); s_state_mutex = NULL; s_task_stopped = NULL; s_rx_stream = NULL; s_tx_stream = NULL; s_rx_stream_storage = NULL; s_tx_stream_storage = NULL; return ESP_ERR_NO_MEM; } s_config = *initial_config; s_static_mode_safe = true; s_initialized = true; return ESP_OK; } esp_err_t serial_service_start(void) { if (!s_initialized) { return ESP_ERR_INVALID_STATE; } xSemaphoreTake(s_state_mutex, portMAX_DELAY); esp_err_t result = rs232_port_claim(RS232_PORT_OWNER_SERVICE); if (result == ESP_OK) { result = start_locked(true); if (result != ESP_OK && rs232_port_get_owner() == RS232_PORT_OWNER_SERVICE && !uart_is_driver_installed(RS232_UART_PORT)) { if (s_static_mode_safe) { rs232_port_release(RS232_PORT_OWNER_SERVICE); } else { gpio_set_level(RS232_FORCE_OFF_N_GPIO, 0); rs232_port_mark_fault(RS232_PORT_OWNER_SERVICE); } } } xSemaphoreGive(s_state_mutex); return result; } esp_err_t serial_service_stop(void) { if (!s_initialized) { return ESP_ERR_INVALID_STATE; } xSemaphoreTake(s_state_mutex, portMAX_DELAY); esp_err_t result = stop_locked(true); if (!s_running && !uart_is_driver_installed(RS232_UART_PORT) && rs232_port_get_owner() == RS232_PORT_OWNER_SERVICE) { if (s_static_mode_safe) { esp_err_t release_error = rs232_port_release(RS232_PORT_OWNER_SERVICE); if (result == ESP_OK) { result = release_error; } } else { gpio_set_level(RS232_FORCE_OFF_N_GPIO, 0); rs232_port_mark_fault(RS232_PORT_OWNER_SERVICE); } } xSemaphoreGive(s_state_mutex); return result; } bool serial_service_is_running(void) { return atomic_load(&s_running); } esp_err_t serial_service_apply_config(const serial_config_t *config) { if (!s_initialized) { return ESP_ERR_INVALID_STATE; } ESP_RETURN_ON_ERROR(serial_config_validate(config), TAG, "Validate new config"); xSemaphoreTake(s_state_mutex, portMAX_DELAY); serial_config_t previous = s_config; bool restart = s_running; esp_err_t result = ESP_OK; if (restart) { result = stop_locked(false); } if (result == ESP_OK) { s_config = *config; if (restart) { result = start_locked(false); if (result != ESP_OK && rs232_port_get_owner() == RS232_PORT_OWNER_SERVICE) { esp_err_t original_error = result; ESP_LOGW(TAG, "New configuration failed; restoring previous UART configuration"); s_config = previous; esp_err_t rollback_error = start_locked(false); if (rollback_error != ESP_OK) { ESP_LOGE(TAG, "Could not restore previous UART configuration: %s", esp_err_to_name(rollback_error)); if (!uart_is_driver_installed(RS232_UART_PORT)) { esp_err_t restore_error = restore_static_mode_or_fault(); if (restore_error == ESP_OK) { rs232_port_release(RS232_PORT_OWNER_SERVICE); } } else { rs232_port_mark_fault(RS232_PORT_OWNER_SERVICE); } } result = original_error; } } } else if (!uart_is_driver_installed(RS232_UART_PORT) && rs232_port_get_owner() == RS232_PORT_OWNER_SERVICE) { /* A failed stop that removed UART1 releases ownership only after safe restoration. */ esp_err_t restore_error = restore_static_mode_or_fault(); if (restore_error == ESP_OK) { rs232_port_release(RS232_PORT_OWNER_SERVICE); } } xSemaphoreGive(s_state_mutex); return result; } esp_err_t serial_service_get_config(serial_config_t *config) { if (!s_initialized || config == NULL) { return ESP_ERR_INVALID_ARG; } xSemaphoreTake(s_state_mutex, portMAX_DELAY); *config = s_config; xSemaphoreGive(s_state_mutex); return ESP_OK; } esp_err_t serial_service_get_snapshot(serial_service_snapshot_t *snapshot) { if (snapshot == NULL) return ESP_ERR_INVALID_ARG; memset(snapshot, 0, sizeof(*snapshot)); if (!s_initialized) return ESP_ERR_INVALID_STATE; if (xSemaphoreTake(s_state_mutex, 0) != pdTRUE) return ESP_ERR_TIMEOUT; snapshot->config = s_config; snapshot->running = atomic_load(&s_running); xSemaphoreGive(s_state_mutex); return ESP_OK; } size_t serial_service_read(uint8_t *data, size_t size) { if (!s_initialized || data == NULL || size == 0) { return 0; } if (xSemaphoreTake(s_state_mutex, 0) != pdTRUE) { return 0; } size_t received = 0; if (s_running && !s_stop_requested) { received = xStreamBufferReceive(s_rx_stream, data, size, 0); } xSemaphoreGive(s_state_mutex); return received; } size_t serial_service_write(const uint8_t *data, size_t size) { if (!s_initialized || data == NULL || size == 0) { return 0; } if (xSemaphoreTake(s_state_mutex, 0) != pdTRUE) { return 0; } size_t accepted = 0; if (s_running && !s_stop_requested) { accepted = xStreamBufferSend(s_tx_stream, data, size, 0); add_counter(&s_counters.tx_queued_bytes, accepted); if (accepted < size) { add_counter(&s_counters.tx_dropped_bytes, size - accepted); } } xSemaphoreGive(s_state_mutex); return accepted; } size_t serial_service_rx_available(void) { if (!s_initialized) { return 0; } xSemaphoreTake(s_state_mutex, portMAX_DELAY); size_t available = xStreamBufferBytesAvailable(s_rx_stream); xSemaphoreGive(s_state_mutex); return available; } size_t serial_service_tx_pending(void) { if (!s_initialized) { return 0; } xSemaphoreTake(s_state_mutex, portMAX_DELAY); size_t pending = xStreamBufferBytesAvailable(s_tx_stream) + atomic_load(&s_tx_task_pending); xSemaphoreGive(s_state_mutex); return pending; } esp_err_t serial_service_set_session_active(bool active) { if (!s_initialized) { return ESP_ERR_INVALID_STATE; } xSemaphoreTake(s_state_mutex, portMAX_DELAY); esp_err_t result = ESP_OK; if (s_running && s_config.dtr_behavior == SERIAL_CONFIG_DTR_ON_CONNECT) { result = gpio_set_level(RS232_DTR_GPIO, active ? 0 : 1); } if (result == ESP_OK) { s_session_active = active; } xSemaphoreGive(s_state_mutex); return result; } void serial_service_get_modem_state(serial_modem_state_t *state) { if (state == NULL) { return; } taskENTER_CRITICAL(&s_counter_lock); *state = s_modem_state; taskEXIT_CRITICAL(&s_counter_lock); } void serial_service_get_counters(serial_service_counters_t *counters) { if (counters == NULL) { return; } taskENTER_CRITICAL(&s_counter_lock); *counters = s_counters; taskEXIT_CRITICAL(&s_counter_lock); } void serial_service_clear_counters(void) { taskENTER_CRITICAL(&s_counter_lock); memset(&s_counters, 0, sizeof(s_counters)); taskEXIT_CRITICAL(&s_counter_lock); }