Add Phase 1 UART service foundation
Add versioned NVS-backed configuration, buffered UART1 I/O, modem monitoring, counters, and serial console controls. Coordinate UART1 ownership with Phase 0 diagnostics and document loopback verification.
This commit is contained in:
@@ -0,0 +1,720 @@
|
||||
#include "serial_service.h"
|
||||
|
||||
#include <stdatomic.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "board_pins.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "driver/uart.h"
|
||||
#include "esp_check.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 16384
|
||||
#define SERIAL_TX_STREAM_SIZE 8192
|
||||
#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 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 = xStreamBufferCreate(SERIAL_RX_STREAM_SIZE, 1);
|
||||
s_tx_stream = xStreamBufferCreate(SERIAL_TX_STREAM_SIZE, 1);
|
||||
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);
|
||||
}
|
||||
s_state_mutex = NULL;
|
||||
s_task_stopped = NULL;
|
||||
s_rx_stream = NULL;
|
||||
s_tx_stream = 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;
|
||||
}
|
||||
|
||||
size_t serial_service_read(uint8_t *data, size_t size)
|
||||
{
|
||||
if (!s_initialized || data == NULL || size == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
xSemaphoreTake(s_state_mutex, portMAX_DELAY);
|
||||
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;
|
||||
}
|
||||
|
||||
xSemaphoreTake(s_state_mutex, portMAX_DELAY);
|
||||
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);
|
||||
s_session_active = active;
|
||||
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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
Reference in New Issue
Block a user