From 4be002e3e3162c6ef8e50ea33c9a058711bea3b2 Mon Sep 17 00:00:00 2001 From: Paul Guyot Date: Sun, 10 Dec 2023 10:09:03 +0100 Subject: [PATCH] Add support for esp_task_wdt API Signed-off-by: Paul Guyot --- libs/eavmlib/src/esp.erl | 94 +++++- .../esp32/components/avm_sys/platform_nifs.c | 271 ++++++++++++++++++ .../test/main/test_erl_sources/CMakeLists.txt | 3 + .../test/main/test_erl_sources/test_twdt.erl | 103 +++++++ src/platforms/esp32/test/main/test_main.c | 8 + 5 files changed, 478 insertions(+), 1 deletion(-) create mode 100644 src/platforms/esp32/test/main/test_erl_sources/test_twdt.erl diff --git a/libs/eavmlib/src/esp.erl b/libs/eavmlib/src/esp.erl index 1a642187f..b3f844c2f 100644 --- a/libs/eavmlib/src/esp.erl +++ b/libs/eavmlib/src/esp.erl @@ -46,7 +46,13 @@ rtc_slow_get_binary/0, rtc_slow_set_binary/1, freq_hz/0, - get_mac/1 + get_mac/1, + task_wdt_init/1, + task_wdt_reconfigure/1, + task_wdt_deinit/0, + task_wdt_add_user/1, + task_wdt_reset_user/1, + task_wdt_delete_user/1 ]). -deprecated([ @@ -101,6 +107,26 @@ -type interface() :: wifi_sta | wifi_softap. -type mac() :: binary(). +-type task_wdt_config() :: { + TimeoutMS :: pos_integer(), + IdleCoreMask :: non_neg_integer(), + TriggerPanic :: boolean() +}. +-opaque task_wdt_user_handle() :: binary(). + +-export_type( + [ + esp_partition/0, + esp_partition_type/0, + esp_partition_subtype/0, + esp_partition_address/0, + esp_partition_size/0, + esp_partition_props/0, + task_wdt_config/0, + task_wdt_user_handle/0 + ] +). + -define(ATOMVM_NVS_NS, atomvm). %%----------------------------------------------------------------------------- @@ -387,3 +413,69 @@ freq_hz() -> -spec get_mac(Interface :: interface()) -> mac(). get_mac(_Interface) -> erlang:nif_error(undefined). + +%%----------------------------------------------------------------------------- +%% @param Config configuration for the watchdog timer +%% @returns ok or an error tuple +%% @doc Initialize the task watchdog timer with a configuration +%% Available with ESP-IDF 5.0 or higher. +%% @end +%%----------------------------------------------------------------------------- +-spec task_wdt_init(Config :: task_wdt_config()) -> ok | {error, already_started} | {error, any()}. +task_wdt_init(_Config) -> + erlang:nif_error(undefined). + +%%----------------------------------------------------------------------------- +%% @param Config configuration for the watchdog timer +%% @returns ok or an error tuple +%% @doc Update the configuration of the task watchdog timer +%% Available with ESP-IDF 5.0 or higher. +%% @end +%%----------------------------------------------------------------------------- +-spec task_wdt_reconfigure(Config :: task_wdt_config()) -> ok | {error, noproc} | {error, any()}. +task_wdt_reconfigure(_Config) -> + erlang:nif_error(undefined). + +%%----------------------------------------------------------------------------- +%% @returns ok or an error tuple if tasks are subscribed (beyond idle tasks) or +%% if the timer is not initialized +%% @doc Deinitialize the task watchdog timer +%% Available with ESP-IDF 5.0 or higher. +%% @end +%%----------------------------------------------------------------------------- +-spec task_wdt_deinit() -> ok | {error, any()}. +task_wdt_deinit() -> + erlang:nif_error(undefined). + +%%----------------------------------------------------------------------------- +%% @param Username name of the user +%% @returns the handle to use with `task_wdt_reset_user/1' or an error tuple. +%% @doc Register a user of the task watchdog timer. +%% Available with ESP-IDF 5.0 or higher. +%% @end +%%----------------------------------------------------------------------------- +-spec task_wdt_add_user(Username :: iodata()) -> {ok, task_wdt_user_handle()} | {error, any()}. +task_wdt_add_user(_Username) -> + erlang:nif_error(undefined). + +%%----------------------------------------------------------------------------- +%% @param UserHandle handle for the user, obtained from `task_wdt_add_user/1' +%% @returns ok or an error tuple +%% @doc Reset the timer a previously registered user. +%% Available with ESP-IDF 5.0 or higher. +%% @end +%%----------------------------------------------------------------------------- +-spec task_wdt_reset_user(UserHandle :: task_wdt_user_handle()) -> ok | {error, any()}. +task_wdt_reset_user(_UserHandle) -> + erlang:nif_error(undefined). + +%%----------------------------------------------------------------------------- +%% @param UserHandle handle for the user, obtained from `task_wdt_add_user/1' +%% @returns ok or an error tuple +%% @doc Unsubscribe a given user from the task watchdog timer. +%% Available with ESP-IDF 5.0 or higher. +%% @end +%%----------------------------------------------------------------------------- +-spec task_wdt_delete_user(UserHandle :: task_wdt_user_handle()) -> ok | {error, any()}. +task_wdt_delete_user(_UserHandle) -> + erlang:nif_error(undefined). diff --git a/src/platforms/esp32/components/avm_sys/platform_nifs.c b/src/platforms/esp32/components/avm_sys/platform_nifs.c index 2d0b1248f..9d807cbc0 100644 --- a/src/platforms/esp32/components/avm_sys/platform_nifs.c +++ b/src/platforms/esp32/components/avm_sys/platform_nifs.c @@ -36,6 +36,7 @@ #include #include #include +#include #include #include #include @@ -54,6 +55,12 @@ #define TAG "atomvm" +#if ESP_IDF_VERSION_MAJOR >= 5 && (CONFIG_ESP_TASK_WDT_EN || CONFIG_ESP_TASK_WDT) +#define ESP_TASK_WDT_API 1 +#else +#define ESP_TASK_WDT_API 0 +#endif + static const char *const esp_rst_unknown_atom = "\xF" "esp_rst_unknown"; static const char *const esp_rst_poweron = "\xF" "esp_rst_poweron"; static const char *const esp_rst_ext = "\xB" "esp_rst_ext"; @@ -65,6 +72,9 @@ static const char *const esp_rst_wdt = "\xB" "esp_rst_wdt"; static const char *const esp_rst_deepsleep = "\x11" "esp_rst_deepsleep"; static const char *const esp_rst_brownout = "\x10" "esp_rst_brownout"; static const char *const esp_rst_sdio = "\xC" "esp_rst_sdio"; +#if ESP_TASK_WDT_API +static const char *const already_started = "\xF" "already_started"; +#endif // 123456789ABCDEF01 enum NetworkInterface { @@ -79,6 +89,13 @@ static const AtomStringIntPair interface_table[] = { SELECT_INT_DEFAULT(InvalidInterface) }; +#if ESP_IDF_VERSION_MAJOR >= 5 +struct esp_task_wdt_user_handle_and_name { + esp_task_wdt_user_handle_t user_handle; + char *user_name; +}; +#endif + #if defined __has_include #if __has_include() #include @@ -492,6 +509,202 @@ static term nif_esp_get_mac(Context *ctx, int argc, term argv[]) return term_from_literal_binary(mac, 6, &ctx->heap, ctx->global); } +#if ESP_TASK_WDT_API +static term parse_task_wdt_config(Context *ctx, esp_task_wdt_config_t *config, term argv[]) +{ + VALIDATE_VALUE(argv[0], term_is_tuple); + size_t tuple_size = term_get_tuple_arity(argv[0]); + if (tuple_size != 3) { + RAISE_ERROR(BADARG_ATOM); + } + term timeout_ms_term = term_get_tuple_element(argv[0], 0); + VALIDATE_VALUE(timeout_ms_term, term_is_integer); + avm_int_t timeout_ms = term_to_int(timeout_ms_term); + if (timeout_ms <= 0) { + RAISE_ERROR(BADARG_ATOM); + } + + term core_mask_term = term_get_tuple_element(argv[0], 1); + VALIDATE_VALUE(core_mask_term, term_is_integer); + avm_int_t core_mask = term_to_int(core_mask_term); + if (core_mask < 0) { + RAISE_ERROR(BADARG_ATOM); + } +#ifdef HAVE_SOC_CPU_CORES_NUM + if (core_mask > 1 << SOC_CPU_CORES_NUM) { + RAISE_ERROR(BADARG_ATOM); + } +#else + if (core_mask > 1 << 2) { + RAISE_ERROR(BADARG_ATOM); + } +#endif + + term trigger_panic_term = term_get_tuple_element(argv[0], 2); + if (trigger_panic_term != TRUE_ATOM && trigger_panic_term != FALSE_ATOM) { + RAISE_ERROR(BADARG_ATOM); + } + + config->timeout_ms = timeout_ms; + config->idle_core_mask = core_mask; + config->trigger_panic = trigger_panic_term != FALSE_ATOM; + + return OK_ATOM; +} + +static term nif_esp_task_wdt_init(Context *ctx, int argc, term argv[]) +{ + UNUSED(argc); + esp_task_wdt_config_t config; + + if (term_is_invalid_term(parse_task_wdt_config(ctx, &config, argv))) { + return term_invalid_term(); + } + + esp_err_t result = esp_task_wdt_init(&config); + if (result == ESP_OK) { + return OK_ATOM; + } + + if (UNLIKELY(memory_ensure_free(ctx, TUPLE_SIZE(2)) != MEMORY_GC_OK)) { + RAISE_ERROR(OUT_OF_MEMORY_ATOM); + } + term error_tuple = term_alloc_tuple(2, &ctx->heap); + term_put_tuple_element(error_tuple, 0, ERROR_ATOM); + if (result == ESP_ERR_INVALID_STATE) { + term_put_tuple_element(error_tuple, 1, globalcontext_make_atom(ctx->global, already_started)); + } else { + term_put_tuple_element(error_tuple, 1, term_from_int(result)); + } + return error_tuple; +} + +static term nif_esp_task_wdt_reconfigure(Context *ctx, int argc, term argv[]) +{ + UNUSED(argc); + esp_task_wdt_config_t config; + + if (term_is_invalid_term(parse_task_wdt_config(ctx, &config, argv))) { + return term_invalid_term(); + } + + esp_err_t result = esp_task_wdt_reconfigure(&config); + if (result == ESP_OK) { + return OK_ATOM; + } + + if (UNLIKELY(memory_ensure_free(ctx, TUPLE_SIZE(2)) != MEMORY_GC_OK)) { + RAISE_ERROR(OUT_OF_MEMORY_ATOM); + } + term error_tuple = term_alloc_tuple(2, &ctx->heap); + term_put_tuple_element(error_tuple, 0, ERROR_ATOM); + if (result == ESP_ERR_INVALID_STATE) { + term_put_tuple_element(error_tuple, 1, NOPROC_ATOM); + } else { + term_put_tuple_element(error_tuple, 1, term_from_int(result)); + } + return error_tuple; +} + +static term nif_esp_task_wdt_deinit(Context *ctx, int argc, term argv[]) +{ + UNUSED(argc); + + esp_err_t result = esp_task_wdt_deinit(); + if (result == ESP_OK) { + return OK_ATOM; + } + + term error_tuple = term_alloc_tuple(2, &ctx->heap); + term_put_tuple_element(error_tuple, 0, ERROR_ATOM); + term_put_tuple_element(error_tuple, 1, term_from_int(result)); + + return error_tuple; +} + +static term nif_esp_task_wdt_add_user(Context *ctx, int argc, term argv[]) +{ + UNUSED(argc); + + int ok; + char *user_name = interop_term_to_string(argv[0], &ok); + if (UNLIKELY(!ok)) { + RAISE_ERROR(BADARG_ATOM); + } + // Documentation isn't explicit about it, but user name must exist while the user is registered + if (UNLIKELY(memory_ensure_free(ctx, TUPLE_SIZE(2) + term_binary_heap_size(sizeof(struct esp_task_wdt_user_handle_and_name))) != MEMORY_GC_OK)) { + RAISE_ERROR(OUT_OF_MEMORY_ATOM); + } + term binary = term_create_empty_binary(sizeof(struct esp_task_wdt_user_handle_and_name), &ctx->heap, ctx->global); + struct esp_task_wdt_user_handle_and_name *handle = (struct esp_task_wdt_user_handle_and_name *) term_binary_data(binary); + handle->user_name = user_name; + + esp_err_t result = esp_task_wdt_add_user(handle->user_name, &handle->user_handle); + + term result_tuple; + if (result == ESP_OK) { + result_tuple = term_alloc_tuple(2, &ctx->heap); + term_put_tuple_element(result_tuple, 0, OK_ATOM); + term_put_tuple_element(result_tuple, 1, binary); + } else { + result_tuple = term_alloc_tuple(2, &ctx->heap); + term_put_tuple_element(result_tuple, 0, ERROR_ATOM); + term_put_tuple_element(result_tuple, 1, term_from_int(result)); + } + return result_tuple; +} + +static term nif_esp_task_wdt_reset_user(Context *ctx, int argc, term argv[]) +{ + UNUSED(argc); + + VALIDATE_VALUE(argv[0], term_is_binary); + size_t binary_size = term_binary_size(argv[0]); + if (binary_size != sizeof(struct esp_task_wdt_user_handle_and_name)) { + RAISE_ERROR(BADARG_ATOM); + } + struct esp_task_wdt_user_handle_and_name *handle = (struct esp_task_wdt_user_handle_and_name *) term_binary_data(argv[0]); + esp_err_t result = esp_task_wdt_reset_user(handle->user_handle); + if (result == ESP_OK) { + return OK_ATOM; + } else { + if (UNLIKELY(memory_ensure_free(ctx, TUPLE_SIZE(2)) != MEMORY_GC_OK)) { + RAISE_ERROR(OUT_OF_MEMORY_ATOM); + } + term error_tuple = term_alloc_tuple(2, &ctx->heap); + term_put_tuple_element(error_tuple, 0, ERROR_ATOM); + term_put_tuple_element(error_tuple, 1, term_from_int(result)); + return error_tuple; + } +} + +static term nif_esp_task_wdt_delete_user(Context *ctx, int argc, term argv[]) +{ + UNUSED(argc); + + VALIDATE_VALUE(argv[0], term_is_binary); + size_t binary_size = term_binary_size(argv[0]); + if (binary_size != sizeof(struct esp_task_wdt_user_handle_and_name)) { + RAISE_ERROR(BADARG_ATOM); + } + struct esp_task_wdt_user_handle_and_name *handle = (struct esp_task_wdt_user_handle_and_name *) term_binary_data(argv[0]); + esp_err_t result = esp_task_wdt_delete_user(handle->user_handle); + if (result == ESP_OK) { + free(handle->user_name); + handle->user_name = NULL; // double free shouldn't happen as esp_task_wdt_delete_user should refuse to delete the user again... + return OK_ATOM; + } else { + if (UNLIKELY(memory_ensure_free(ctx, TUPLE_SIZE(2)) != MEMORY_GC_OK)) { + RAISE_ERROR(OUT_OF_MEMORY_ATOM); + } + term error_tuple = term_alloc_tuple(2, &ctx->heap); + term_put_tuple_element(error_tuple, 0, ERROR_ATOM); + term_put_tuple_element(error_tuple, 1, term_from_int(result)); + return error_tuple; + } +} +#endif + // // NIF structures and dispatch // @@ -575,6 +788,38 @@ static const struct Nif esp_get_mac_nif = .base.type = NIFFunctionType, .nif_ptr = nif_esp_get_mac }; +#if ESP_TASK_WDT_API +static const struct Nif esp_task_wdt_init_nif = +{ + .base.type = NIFFunctionType, + .nif_ptr = nif_esp_task_wdt_init +}; +static const struct Nif esp_task_wdt_reconfigure_nif = +{ + .base.type = NIFFunctionType, + .nif_ptr = nif_esp_task_wdt_reconfigure +}; +static const struct Nif esp_task_wdt_deinit_nif = +{ + .base.type = NIFFunctionType, + .nif_ptr = nif_esp_task_wdt_deinit +}; +static const struct Nif esp_task_wdt_add_user_nif = +{ + .base.type = NIFFunctionType, + .nif_ptr = nif_esp_task_wdt_add_user +}; +static const struct Nif esp_task_wdt_reset_user_nif = +{ + .base.type = NIFFunctionType, + .nif_ptr = nif_esp_task_wdt_reset_user +}; +static const struct Nif esp_task_wdt_delete_user_nif = +{ + .base.type = NIFFunctionType, + .nif_ptr = nif_esp_task_wdt_delete_user +}; +#endif const struct Nif *platform_nifs_get_nif(const char *nifname) { @@ -650,6 +895,32 @@ const struct Nif *platform_nifs_get_nif(const char *nifname) TRACE("Resolved platform nif %s ...\n", nifname); return &esp_get_mac_nif; } +#if ESP_TASK_WDT_API + if (strcmp("esp:task_wdt_init/1", nifname) == 0) { + TRACE("Resolved platform nif %s ...\n", nifname); + return &esp_task_wdt_init_nif; + } + if (strcmp("esp:task_wdt_reconfigure/1", nifname) == 0) { + TRACE("Resolved platform nif %s ...\n", nifname); + return &esp_task_wdt_reconfigure_nif; + } + if (strcmp("esp:task_wdt_deinit/0", nifname) == 0) { + TRACE("Resolved platform nif %s ...\n", nifname); + return &esp_task_wdt_deinit_nif; + } + if (strcmp("esp:task_wdt_add_user/1", nifname) == 0) { + TRACE("Resolved platform nif %s ...\n", nifname); + return &esp_task_wdt_add_user_nif; + } + if (strcmp("esp:task_wdt_reset_user/1", nifname) == 0) { + TRACE("Resolved platform nif %s ...\n", nifname); + return &esp_task_wdt_reset_user_nif; + } + if (strcmp("esp:task_wdt_delete_user/1", nifname) == 0) { + TRACE("Resolved platform nif %s ...\n", nifname); + return &esp_task_wdt_delete_user_nif; + } +#endif const struct Nif *nif = nif_collection_resolve_nif(nifname); if (nif) { return nif; diff --git a/src/platforms/esp32/test/main/test_erl_sources/CMakeLists.txt b/src/platforms/esp32/test/main/test_erl_sources/CMakeLists.txt index fb907ded1..4cc13d62b 100644 --- a/src/platforms/esp32/test/main/test_erl_sources/CMakeLists.txt +++ b/src/platforms/esp32/test/main/test_erl_sources/CMakeLists.txt @@ -49,6 +49,7 @@ compile_erlang(test_select) compile_erlang(test_socket) compile_erlang(test_ssl) compile_erlang(test_time_and_processes) +compile_erlang(test_twdt) compile_erlang(test_tz) add_custom_command( @@ -67,6 +68,7 @@ add_custom_command( test_socket.beam test_ssl.beam test_time_and_processes.beam + test_twdt.beam test_tz.beam DEPENDS HostAtomVM @@ -82,6 +84,7 @@ add_custom_command( "${CMAKE_CURRENT_BINARY_DIR}/test_socket.beam" "${CMAKE_CURRENT_BINARY_DIR}/test_ssl.beam" "${CMAKE_CURRENT_BINARY_DIR}/test_time_and_processes.beam" + "${CMAKE_CURRENT_BINARY_DIR}/test_twdt.beam" "${CMAKE_CURRENT_BINARY_DIR}/test_tz.beam" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} VERBATIM diff --git a/src/platforms/esp32/test/main/test_erl_sources/test_twdt.erl b/src/platforms/esp32/test/main/test_erl_sources/test_twdt.erl new file mode 100644 index 000000000..cfd810ec4 --- /dev/null +++ b/src/platforms/esp32/test/main/test_erl_sources/test_twdt.erl @@ -0,0 +1,103 @@ +% +% This file is part of AtomVM. +% +% Copyright 2023 Paul Guyot +% +% Licensed under the Apache License, Version 2.0 (the "License"); +% you may not use this file except in compliance with the License. +% You may obtain a copy of the License at +% +% http://www.apache.org/licenses/LICENSE-2.0 +% +% Unless required by applicable law or agreed to in writing, software +% distributed under the License is distributed on an "AS IS" BASIS, +% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +% See the License for the specific language governing permissions and +% limitations under the License. +% +% SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later +% + +-module(test_twdt). +-export([start/0]). + +start() -> + ok = test_reconfigure(), + ok = test_user(), + ok = test_deinit_init(), + ok. + +test_reconfigure() -> + ok = + try + esp:task_wdt_reconfigure(config), + fail + catch + error:badarg -> ok + end, + ok = + try + esp:task_wdt_reconfigure({-1, 0, false}), + fail + catch + error:badarg -> ok + end, + ok = + try + esp:task_wdt_reconfigure({0, 0, false}), + fail + catch + error:badarg -> ok + end, + ok = + try + esp:task_wdt_reconfigure({5000, -1, false}), + fail + catch + error:badarg -> ok + end, + ok = + try + esp:task_wdt_reconfigure({5000, 512, false}), + fail + catch + error:badarg -> ok + end, + ok = + try + esp:task_wdt_reconfigure({5000, 0, 0}), + fail + catch + error:badarg -> ok + end, + ok = esp:task_wdt_reconfigure({5000, 0, false}), + ok. + +test_user() -> + ok = + try + esp:task_wdt_add_user(42), + fail + catch + error:badarg -> ok + end, + {ok, Handle} = esp:task_wdt_add_user(<<"42">>), + io:format("Reconfigure to 100ms\n"), + ok = esp:task_wdt_reconfigure({100, 0, false}), + io:format("Reset now\n"), + ok = esp:task_wdt_reset_user(Handle), + io:format("Wait 150ms\n"), + timer:sleep(150), + io:format("Reset again\n"), + ok = esp:task_wdt_reset_user(Handle), + ok = esp:task_wdt_delete_user(Handle), + ok = esp:task_wdt_reconfigure({5000, 0, false}), + ok. + +test_deinit_init() -> + ok = esp:task_wdt_deinit(), + {error, _} = esp:task_wdt_deinit(), + {error, noproc} = esp:task_wdt_reconfigure({5000, 0, false}), + ok = esp:task_wdt_init({5000, 0, false}), + {error, already_started} = esp:task_wdt_init({5000, 0, false}), + ok. diff --git a/src/platforms/esp32/test/main/test_main.c b/src/platforms/esp32/test/main/test_main.c index 7bedbcded..c39378bf9 100644 --- a/src/platforms/esp32/test/main/test_main.c +++ b/src/platforms/esp32/test/main/test_main.c @@ -490,6 +490,14 @@ TEST_CASE("test_rtc_slow", "[test_run]") TEST_ASSERT(term_to_int(ret_value) == 0); } +#if ESP_IDF_VERSION_MAJOR >= 5 +TEST_CASE("test_twdt", "[test_run]") +{ + term ret_value = avm_test_case("test_twdt.beam"); + TEST_ASSERT(ret_value == OK_ATOM); +} +#endif + void app_main(void) { UNITY_BEGIN();