Skip to content

Commit 518d18e

Browse files
committed
Fix concurrency issues and error handling in ESP32 UART driver
Fixes possible concurrency problems by using a mutex for access to `reader_process_pid` and `reader_ref_ticks` in `uart_data`. Avoid possible overflow of the queue by handling all mesages in the interrupt callback. Now cleans up and returns errors, with improved log messages when initalization fails with a badarg in the configuration parameters, rather than aborting. Fixes incorrect pin integers being cast to terms. Pin numbers are validated for the chip the VM is compiled for before use to mitigate VM crashes from bad input parameters. Signed-off-by: Winford <[email protected]>
1 parent 7b1688d commit 518d18e

File tree

2 files changed

+111
-55
lines changed

2 files changed

+111
-55
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,11 @@ and a race condition in otp_socket code
5454
- Fixed an out of memory issue by forcing GC to copy data from message fragments
5555
- Fixed a bug where calling repeatedly `process_info` on a stopped process could cause an out of
5656
memory error
57+
- Fixed possible concurrency problems in ESP32 UART driver
58+
59+
### Changed
60+
61+
- ESP32 UART driver no longer aborts because of badargs in configuration, instead raising an error
5762

5863
## [0.6.5] - 2024-10-15
5964

src/platforms/esp32/components/avm_builtins/uart_driver.c

Lines changed: 106 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,14 @@
2121
#include <sdkconfig.h>
2222
#ifdef CONFIG_AVM_ENABLE_UART_PORT_DRIVER
2323

24+
#include <assert.h>
2425
#include <string.h>
2526

2627
#include <driver/uart.h>
27-
2828
#include <esp_log.h>
29-
#include <freertos/FreeRTOS.h>
30-
#include <freertos/task.h>
29+
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 2, 0)
30+
#include <soc/gpio_num.h>
31+
#endif
3132

3233
#include "atom.h"
3334
#include "bif.h"
@@ -38,9 +39,10 @@
3839
#include "interop.h"
3940
#include "mailbox.h"
4041
#include "module.h"
41-
#include "port.h"
4242
#include "platform_defaultatoms.h"
43+
#include "port.h"
4344
#include "scheduler.h"
45+
#include "smp.h"
4446
#include "term.h"
4547
#include "utils.h"
4648

@@ -50,13 +52,17 @@
5052
#include "sys.h"
5153

5254
static Context *uart_driver_create_port(GlobalContext *global, term opts);
53-
54-
static const char *const ealready_atom = ATOM_STR("\x8", "ealready");
55-
static const char *const no_proc_atom = ATOM_STR("\x7", "no_proc");
5655
static NativeHandlerResult uart_driver_consume_mailbox(Context *ctx);
5756

5857
#define TAG "uart_driver"
5958
#define UART_BUF_SIZE 256
59+
#define NO_REF 0
60+
#define NO_READER term_invalid_term()
61+
#define PIN_ERROR -2
62+
63+
#ifndef GPIO_NUM_MAX // remove conditional after ESP_IDF v5.1 is deprecated
64+
#define GPIO_NUM_MAX SOC_GPIO_PIN_COUNT
65+
#endif
6066

6167
struct UARTData
6268
{
@@ -65,6 +71,9 @@ struct UARTData
6571
term reader_process_pid;
6672
uint64_t reader_ref_ticks;
6773
uint8_t uart_num;
74+
#ifndef AVM_NO_SMP
75+
Mutex *reader_lock;
76+
#endif
6877
};
6978

7079
static const AtomStringIntPair parity_table[] = {
@@ -86,7 +95,8 @@ enum uart_cmd
8695
UARTInvalidCmd = 0,
8796
UARTReadCmd = 1,
8897
UARTWriteCmd = 2,
89-
UARTCloseCmd = 3
98+
UARTCloseCmd = 3,
99+
UARTCancelCmd = 4
90100
};
91101

92102
static const AtomStringIntPair cmd_table[] = {
@@ -96,13 +106,22 @@ static const AtomStringIntPair cmd_table[] = {
96106
SELECT_INT_DEFAULT(UARTInvalidCmd)
97107
};
98108

109+
static void safe_update_reader_data(struct UARTData *uart_data, term pid, uint64_t ref_ticks)
110+
{
111+
SMP_MUTEX_LOCK(uart_data->reader_lock);
112+
uart_data->reader_process_pid = pid;
113+
uart_data->reader_ref_ticks = ref_ticks;
114+
SMP_MUTEX_UNLOCK(uart_data->reader_lock);
115+
}
116+
99117
EventListener *uart_interrupt_callback(GlobalContext *glb, EventListener *listener)
100118
{
101119
struct UARTData *uart_data = GET_LIST_ENTRY(listener, struct UARTData, listener);
102120

103121
uart_event_t event;
104-
if (xQueueReceive(uart_data->rxqueue, (void *) &event, (TickType_t) portMAX_DELAY)) {
122+
if (xQueueReceive(uart_data->rxqueue, (void *) &event, (TickType_t) 0)) {
105123
switch (event.type) {
124+
case UART_DATA_BREAK:
106125
case UART_DATA:
107126
if (uart_data->reader_process_pid != term_invalid_term()) {
108127
int bin_size = term_binary_heap_size(event.size);
@@ -131,40 +150,50 @@ EventListener *uart_interrupt_callback(GlobalContext *glb, EventListener *listen
131150
globalcontext_send_message(glb, local_pid, result_tuple);
132151

133152
memory_destroy_heap(&heap, glb);
134-
135-
uart_data->reader_process_pid = term_invalid_term();
136-
uart_data->reader_ref_ticks = 0;
153+
safe_update_reader_data(uart_data, NO_READER, NO_REF);
137154
}
138155
break;
139156
case UART_FIFO_OVF:
157+
ESP_LOGE(TAG, "FIFO overflow!");
140158
break;
141159
case UART_BUFFER_FULL:
160+
ESP_LOGW(TAG, "buffer is full!");
142161
break;
143162
case UART_BREAK:
163+
// TODO: handle single NULL char event?, or treat this like UART_DATA & UART_DATA_BREAK?
144164
break;
145165
case UART_PARITY_ERR:
166+
ESP_LOGE(TAG, "parity error detected!");
146167
break;
147168
case UART_FRAME_ERR:
169+
ESP_LOGE(TAG, "frame error detected!");
148170
break;
149171
case UART_PATTERN_DET:
172+
// MAYBE: add pattern detection for incoming uart messages
150173
break;
151174
default:
152175
break;
153176
}
154177
}
178+
155179
return listener;
156180
}
157181

158182
static int get_uart_pin_opt(term opts, term pin_name)
159183
{
184+
_Static_assert(PIN_ERROR < UART_PIN_NO_CHANGE);
160185
term value = interop_proplist_get_value_default(opts, pin_name, DEFAULT_ATOM);
161186
if (value == DEFAULT_ATOM) {
162187
return UART_PIN_NO_CHANGE;
163188
} else if (!term_is_integer(value)) {
164-
// TODO: let's return -2;
165-
fprintf(stderr, "abort() at %s:%i.\n", __FILE__, __LINE__);
166-
abort();
189+
ESP_LOGE(TAG, "pin must be an integer!");
190+
return PIN_ERROR;
167191
} else {
192+
int pin_num = term_to_int(value);
193+
if (UNLIKELY(pin_num < UART_PIN_NO_CHANGE || pin_num > GPIO_NUM_MAX)) {
194+
ESP_LOGE(TAG, "pin number %i is out of range [default | -1..%i]", pin_num, GPIO_NUM_MAX);
195+
return PIN_ERROR;
196+
}
168197
return term_to_int(value);
169198
}
170199
}
@@ -182,22 +211,28 @@ Context *uart_driver_create_port(GlobalContext *global, term opts)
182211
term flow_control_term = interop_proplist_get_value_default(opts, FLOW_CONTROL_ATOM, NONE_ATOM);
183212
term parity_term = interop_proplist_get_value_default(opts, PARITY_ATOM, NONE_ATOM);
184213

185-
term tx_pin = get_uart_pin_opt(opts, TX_ATOM);
186-
term rx_pin = get_uart_pin_opt(opts, RX_ATOM);
187-
term rts_pin = get_uart_pin_opt(opts, RTS_ATOM);
188-
term cts_pin = get_uart_pin_opt(opts, CTS_ATOM);
214+
int tx_pin = get_uart_pin_opt(opts, TX_ATOM);
215+
int rx_pin = get_uart_pin_opt(opts, RX_ATOM);
216+
int rts_pin = get_uart_pin_opt(opts, RTS_ATOM);
217+
int cts_pin = get_uart_pin_opt(opts, CTS_ATOM);
218+
if (UNLIKELY(tx_pin == PIN_ERROR || rx_pin == PIN_ERROR
219+
|| rts_pin == PIN_ERROR || cts_pin == PIN_ERROR)) {
220+
// error already logged in get_uart_pin_opts
221+
return NULL;
222+
}
189223

190224
term event_queue_len_term = interop_proplist_get_value_default(opts, EVENT_QUEUE_LEN_ATOM, term_from_int(16));
191225
if (!term_is_integer(event_queue_len_term)) {
192-
fprintf(stderr, "abort() at %s:%i.\n", __FILE__, __LINE__);
193-
abort();
226+
ESP_LOGE(TAG, "event_queue_len must be an integer");
227+
return NULL;
194228
}
195229
int event_queue_len = term_to_int(event_queue_len_term);
196230

197231
int ok;
198232
char *uart_name = interop_term_to_string(uart_name_term, &ok);
199233
if (!uart_name || !ok) {
200-
AVM_ABORT();
234+
ESP_LOGE(TAG, "name must be character or binary string");
235+
return NULL;
201236
}
202237

203238
uint8_t uart_num;
@@ -206,13 +241,15 @@ Context *uart_driver_create_port(GlobalContext *global, term opts)
206241
} else if (!strcmp(uart_name, "UART1")) {
207242
uart_num = UART_NUM_1;
208243
}
209-
#if defined(CONFIG_IDF_TARGET_ESP32) || defined(CONFIG_IDF_TARGET_ESP32S3)
210-
else if (!strcmp(uart_name, "UART2")) {
244+
#if defined(CONFIG_IDF_TARGET_ESP32) || defined(CONFIG_IDF_TARGET_ESP32S3)
245+
else if (!strcmp(uart_name, "UART2")) {
211246
uart_num = UART_NUM_2;
212247
}
213-
#endif
248+
#endif
214249
else {
215-
AVM_ABORT();
250+
free(uart_name);
251+
ESP_LOGE(TAG, "invalid uart bus name!");
252+
return NULL;
216253
}
217254
free(uart_name);
218255

@@ -233,7 +270,8 @@ Context *uart_driver_create_port(GlobalContext *global, term opts)
233270
data_bits = UART_DATA_5_BITS;
234271
break;
235272
default:
236-
AVM_ABORT();
273+
ESP_LOGE(TAG, "invalid data_bits!");
274+
return NULL;
237275
}
238276

239277
int stop_bits;
@@ -245,17 +283,20 @@ Context *uart_driver_create_port(GlobalContext *global, term opts)
245283
stop_bits = UART_STOP_BITS_2;
246284
break;
247285
default:
248-
AVM_ABORT();
286+
ESP_LOGE(TAG, "invalid stop_bits!");
287+
return NULL;
249288
}
250289

251290
uart_hw_flowcontrol_t flow_control = interop_atom_term_select_int(flow_control_table, flow_control_term, ctx->global);
252291
if (flow_control < 0) {
253-
AVM_ABORT();
292+
ESP_LOGE(TAG, "invalid flow_control!");
293+
return NULL;
254294
}
255295

256296
uart_parity_t parity = interop_atom_term_select_int(parity_table, parity_term, ctx->global);
257297
if (parity < 0) {
258-
AVM_ABORT();
298+
ESP_LOGE(TAG, "invalid parity!");
299+
return NULL;
259300
}
260301

261302
uart_config_t uart_config = {
@@ -272,9 +313,10 @@ Context *uart_driver_create_port(GlobalContext *global, term opts)
272313

273314
uart_set_pin(uart_num, tx_pin, rx_pin, rts_pin, cts_pin);
274315

275-
struct UARTData *uart_data = malloc(sizeof(struct UARTData));
316+
size_t alloc_size = sizeof(struct UARTData);
317+
struct UARTData *uart_data = malloc(alloc_size);
276318
if (IS_NULL_PTR(uart_data)) {
277-
fprintf(stderr, "Failed to allocate memory: %s:%i.\n", __FILE__, __LINE__);
319+
fprintf(stderr, "Failed to allocate memory size %i: %s:%i.\n", (int) alloc_size, __FILE__, __LINE__);
278320
AVM_ABORT();
279321
}
280322
uart_data->listener.handler = uart_interrupt_callback;
@@ -286,15 +328,21 @@ Context *uart_driver_create_port(GlobalContext *global, term opts)
286328
ctx->platform_data = uart_data;
287329

288330
if (uart_driver_install(uart_num, UART_BUF_SIZE, 0, event_queue_len, &uart_data->rxqueue, 0) != ESP_OK) {
289-
fprintf(stderr, "abort() at %s:%i.\n", __FILE__, __LINE__);
290-
abort();
331+
ESP_LOGE(TAG, "failed to install uart driver.");
332+
free(uart_data);
333+
return NULL;
291334
}
292335
uart_data->listener.sender = uart_data->rxqueue;
293336
if (xQueueAddToSet(uart_data->rxqueue, event_set) != pdPASS) {
294-
fprintf(stderr, "abort() at %s:%i.\n", __FILE__, __LINE__);
295-
abort();
337+
ESP_LOGE(TAG, "failed to establish uart queue.");
338+
free(uart_data);
339+
return NULL;
296340
}
297341

342+
#ifndef AVM_NO_SMP
343+
uart_data->reader_lock = smp_mutex_create();
344+
#endif
345+
298346
return ctx;
299347
}
300348

@@ -309,13 +357,13 @@ static void uart_driver_do_read(Context *ctx, GenMessage gen_message)
309357
int local_pid = term_to_local_process_id(pid);
310358

311359
if (uart_data->reader_process_pid != term_invalid_term()) {
312-
if (UNLIKELY(memory_ensure_free_with_roots(ctx, TUPLE_SIZE(2) * 2 , 1, &ref, MEMORY_CAN_SHRINK) != MEMORY_GC_OK)) {
360+
if (UNLIKELY(memory_ensure_free_with_roots(ctx, TUPLE_SIZE(2) * 2, 1, &ref, MEMORY_CAN_SHRINK) != MEMORY_GC_OK)) {
313361
ESP_LOGE(TAG, "[uart_driver_do_read] Failed to allocate space for error tuple");
314-
globalcontext_send_message(glb, local_pid, MEMORY_ATOM);
362+
globalcontext_send_message(glb, local_pid, OUT_OF_MEMORY_ATOM);
315363
return;
316364
}
317365

318-
term ealready = globalcontext_make_atom(glb, ealready_atom);
366+
term ealready = globalcontext_make_atom(glb, ATOM_STR("\x8", "ealready"));
319367
term error_tuple = port_create_error_tuple(ctx, ealready);
320368
port_send_reply(ctx, pid, ref, error_tuple);
321369
return;
@@ -328,7 +376,7 @@ static void uart_driver_do_read(Context *ctx, GenMessage gen_message)
328376
int bin_size = term_binary_heap_size(count);
329377
if (UNLIKELY(memory_ensure_free_with_roots(ctx, bin_size + TUPLE_SIZE(2) * 2, 1, &ref, MEMORY_CAN_SHRINK) != MEMORY_GC_OK)) {
330378
ESP_LOGE(TAG, "[uart_driver_do_read] Failed to allocate space for return value");
331-
globalcontext_send_message(glb, local_pid, MEMORY_ATOM);
379+
globalcontext_send_message(glb, local_pid, OUT_OF_MEMORY_ATOM);
332380
}
333381

334382
term bin = term_create_uninitialized_binary(count, &ctx->heap, glb);
@@ -342,8 +390,7 @@ static void uart_driver_do_read(Context *ctx, GenMessage gen_message)
342390
port_send_reply(ctx, pid, ref, ok_tuple);
343391

344392
} else {
345-
uart_data->reader_process_pid = pid;
346-
uart_data->reader_ref_ticks = ref_ticks;
393+
safe_update_reader_data(uart_data, pid, ref_ticks);
347394
}
348395
}
349396

@@ -389,7 +436,7 @@ static void uart_driver_do_write(Context *ctx, GenMessage gen_message)
389436
int local_pid = term_to_local_process_id(pid);
390437
if (UNLIKELY(memory_ensure_free_with_roots(ctx, TUPLE_SIZE(2), 1, &ref, MEMORY_CAN_SHRINK) != MEMORY_GC_OK)) {
391438
ESP_LOGE(TAG, "[uart_driver_do_write] Failed to allocate space for return value");
392-
globalcontext_send_message(glb, local_pid, MEMORY_ATOM);
439+
globalcontext_send_message(glb, local_pid, OUT_OF_MEMORY_ATOM);
393440
}
394441

395442
port_send_reply(ctx, pid, ref, OK_ATOM);
@@ -402,21 +449,26 @@ static void uart_driver_do_close(Context *ctx, GenMessage gen_message)
402449
term pid = gen_message.pid;
403450
term ref = gen_message.ref;
404451

405-
int local_pid = term_to_local_process_id(pid);
406-
407452
sys_unregister_listener(glb, &uart_data->listener);
453+
#ifndef AVM_NO_SMP
454+
smp_mutex_destroy(uart_data->reader_lock);
455+
#endif
456+
457+
term result;
458+
esp_err_t err = uart_driver_delete(uart_data->uart_num);
459+
if (UNLIKELY(err != ESP_OK)) {
460+
ESP_LOGE(TAG, "Failed to delete UART driver. Error: %s", esp_err_to_name(err));
461+
result = ERROR_ATOM;
462+
} else {
463+
result = OK_ATOM;
464+
}
408465

409466
if (UNLIKELY(memory_ensure_free_with_roots(ctx, TUPLE_SIZE(2), 1, &ref, MEMORY_CAN_SHRINK) != MEMORY_GC_OK)) {
410467
ESP_LOGE(TAG, "[uart_driver_do_close] Failed to allocate space for return value");
411-
globalcontext_send_message(glb, local_pid, MEMORY_ATOM);
468+
globalcontext_send_message(glb, term_to_local_process_id(pid), OUT_OF_MEMORY_ATOM);
412469
}
413470

414-
port_send_reply(ctx, pid, ref, OK_ATOM);
415-
416-
esp_err_t err = uart_driver_delete(uart_data->uart_num);
417-
if (UNLIKELY(err != ESP_OK)) {
418-
ESP_LOGW(TAG, "Failed to delete UART driver. err=%i\n", err);
419-
}
471+
port_send_reply(ctx, pid, ref, result);
420472

421473
free(uart_data);
422474
ctx->platform_data = NULL;
@@ -443,13 +495,12 @@ static NativeHandlerResult uart_driver_consume_mailbox(Context *ctx)
443495
if (is_closed) {
444496
if (UNLIKELY(memory_ensure_free(ctx, TUPLE_SIZE(2) * 2 + REF_SIZE) != MEMORY_GC_OK)) {
445497
ESP_LOGE(TAG, "[uart_driver_consume_mailbox] Failed to allocate space for error tuple");
446-
globalcontext_send_message(glb, local_pid, MEMORY_ATOM);
498+
globalcontext_send_message(glb, local_pid, OUT_OF_MEMORY_ATOM);
447499
}
448500

449-
term no_proc = globalcontext_make_atom(glb, no_proc_atom);
450501
term error_tuple = term_alloc_tuple(2, &ctx->heap);
451502
term_put_tuple_element(error_tuple, 0, ERROR_ATOM);
452-
term_put_tuple_element(error_tuple, 1, no_proc);
503+
term_put_tuple_element(error_tuple, 1, NOPROC_ATOM);
453504

454505
term result_tuple = term_alloc_tuple(2, &ctx->heap);
455506
term_put_tuple_element(result_tuple, 0, term_from_ref_ticks(ref_ticks, &ctx->heap));

0 commit comments

Comments
 (0)