From 6630f7fc0745e207896234647b1c68ca1866f5cc Mon Sep 17 00:00:00 2001 From: Giuliano Franchetto Date: Mon, 3 Jan 2022 12:32:18 +0100 Subject: [PATCH] lorawan: adding settings NVM for LoRaWAN Adding a reference implementation of the Non-Volatile Memory module needed to join any LoRaWAN network. This NVM is based on the SETTINGS subsys to store all the required key to join and communicate on a LoRaWAN network. Without proper NVM, one may experience errors when using OTAA to join the network, as the device may violate anti-replay protection (depending on the version of LoRaWAN). Signed-off-by: Giuliano Franchetto --- subsys/lorawan/CMakeLists.txt | 1 + subsys/lorawan/Kconfig | 2 + subsys/lorawan/lorawan.c | 61 ++++--- subsys/lorawan/nvm/CMakeLists.txt | 3 + subsys/lorawan/nvm/Kconfig | 23 +++ subsys/lorawan/nvm/lorawan_nvm.h | 51 ++++++ subsys/lorawan/nvm/lorawan_nvm_settings.c | 186 ++++++++++++++++++++++ 7 files changed, 305 insertions(+), 22 deletions(-) create mode 100644 subsys/lorawan/nvm/CMakeLists.txt create mode 100644 subsys/lorawan/nvm/Kconfig create mode 100644 subsys/lorawan/nvm/lorawan_nvm.h create mode 100644 subsys/lorawan/nvm/lorawan_nvm_settings.c diff --git a/subsys/lorawan/CMakeLists.txt b/subsys/lorawan/CMakeLists.txt index 898769015cc..3b3c18355cf 100644 --- a/subsys/lorawan/CMakeLists.txt +++ b/subsys/lorawan/CMakeLists.txt @@ -22,3 +22,4 @@ zephyr_compile_definitions_ifdef(CONFIG_LORAMAC_REGION_RU864 REGION_RU864) zephyr_library_sources_ifdef(CONFIG_LORAWAN lorawan.c) zephyr_library_sources_ifdef(CONFIG_LORAWAN lw_priv.c) +add_subdirectory(nvm) diff --git a/subsys/lorawan/Kconfig b/subsys/lorawan/Kconfig index 490733f4e40..0d54f0b3cb4 100644 --- a/subsys/lorawan/Kconfig +++ b/subsys/lorawan/Kconfig @@ -66,4 +66,6 @@ config LORAMAC_REGION_RU864 endchoice +rsource "nvm/Kconfig" + endif # LORAWAN diff --git a/subsys/lorawan/lorawan.c b/subsys/lorawan/lorawan.c index c10b3f57ac9..099106722c8 100644 --- a/subsys/lorawan/lorawan.c +++ b/subsys/lorawan/lorawan.c @@ -7,38 +7,38 @@ #include #include #include -#include #include "lw_priv.h" #include #include +#include "nvm/lorawan_nvm.h" BUILD_ASSERT(!IS_ENABLED(CONFIG_LORAMAC_REGION_UNKNOWN), "Unknown region specified for LoRaWAN in Kconfig"); #ifdef CONFIG_LORAMAC_REGION_AS923 - #define LORAWAN_REGION LORAMAC_REGION_AS923 +#define LORAWAN_REGION LORAMAC_REGION_AS923 #elif CONFIG_LORAMAC_REGION_AU915 - #define LORAWAN_REGION LORAMAC_REGION_AU915 +#define LORAWAN_REGION LORAMAC_REGION_AU915 #elif CONFIG_LORAMAC_REGION_CN470 - #define LORAWAN_REGION LORAMAC_REGION_CN470 +#define LORAWAN_REGION LORAMAC_REGION_CN470 #elif CONFIG_LORAMAC_REGION_CN779 - #define LORAWAN_REGION LORAMAC_REGION_CN779 +#define LORAWAN_REGION LORAMAC_REGION_CN779 #elif CONFIG_LORAMAC_REGION_EU433 - #define LORAWAN_REGION LORAMAC_REGION_EU433 +#define LORAWAN_REGION LORAMAC_REGION_EU433 #elif CONFIG_LORAMAC_REGION_EU868 - #define LORAWAN_REGION LORAMAC_REGION_EU868 +#define LORAWAN_REGION LORAMAC_REGION_EU868 #elif CONFIG_LORAMAC_REGION_KR920 - #define LORAWAN_REGION LORAMAC_REGION_KR920 +#define LORAWAN_REGION LORAMAC_REGION_KR920 #elif CONFIG_LORAMAC_REGION_IN865 - #define LORAWAN_REGION LORAMAC_REGION_IN865 +#define LORAWAN_REGION LORAMAC_REGION_IN865 #elif CONFIG_LORAMAC_REGION_US915 - #define LORAWAN_REGION LORAMAC_REGION_US915 +#define LORAWAN_REGION LORAMAC_REGION_US915 #elif CONFIG_LORAMAC_REGION_RU864 - #define LORAWAN_REGION LORAMAC_REGION_RU864 +#define LORAWAN_REGION LORAMAC_REGION_RU864 #else - #error "At least one LoRaWAN region should be selected" +#error "At least one LoRaWAN region should be selected" #endif /* Use version 1.0.3.0 for ABP */ @@ -212,13 +212,17 @@ static LoRaMacStatus_t lorawan_join_otaa( mlme_req.Req.Join.Datarate = default_datarate; mlme_req.Req.Join.NetworkActivation = ACTIVATION_TYPE_OTAA; - /* Retrieve the NVM context to store device nonce */ - mib_req.Type = MIB_NVM_CTXS; - if (LoRaMacMibGetRequestConfirm(&mib_req) != LORAMAC_STATUS_OK) { - LOG_ERR("Could not get NVM context"); - return -EINVAL; + if (IS_ENABLED(CONFIG_LORAWAN_NVM_NONE)) { + /* Retrieve the NVM context to store device nonce */ + mib_req.Type = MIB_NVM_CTXS; + if (LoRaMacMibGetRequestConfirm(&mib_req) != + LORAMAC_STATUS_OK) { + LOG_ERR("Could not get NVM context"); + return -EINVAL; + } + mib_req.Param.Contexts->Crypto.DevNonce = + join_cfg->otaa.dev_nonce; } - mib_req.Param.Contexts->Crypto.DevNonce = join_cfg->otaa.dev_nonce; mib_req.Type = MIB_DEV_EUI; mib_req.Param.DevEui = join_cfg->dev_eui; @@ -414,7 +418,7 @@ void lorawan_get_payload_sizes(uint8_t *max_next_payload_size, LoRaMacTxInfo_t txInfo; /* QueryTxPossible cannot fail */ - (void)LoRaMacQueryTxPossible(0, &txInfo); + (void) LoRaMacQueryTxPossible(0, &txInfo); *max_next_payload_size = txInfo.MaxPossibleApplicationDataSize; *max_payload_size = txInfo.CurrentPossiblePayloadSize; @@ -456,7 +460,8 @@ int lorawan_set_conf_msg_tries(uint8_t tries) return 0; } -int lorawan_send(uint8_t port, uint8_t *data, uint8_t len, enum lorawan_message_type type) +int lorawan_send(uint8_t port, uint8_t *data, uint8_t len, + enum lorawan_message_type type) { LoRaMacStatus_t status; McpsReq_t mcpsReq; @@ -584,6 +589,8 @@ int lorawan_start(void) static int lorawan_init(const struct device *dev) { + ARG_UNUSED(dev); + LoRaMacStatus_t status; sys_slist_init(&dl_callbacks); @@ -594,7 +601,13 @@ static int lorawan_init(const struct device *dev) macPrimitives.MacMlmeIndication = MlmeIndication; macCallbacks.GetBatteryLevel = getBatteryLevelLocal; macCallbacks.GetTemperatureLevel = NULL; - macCallbacks.NvmDataChange = NULL; + + if (IS_ENABLED(CONFIG_LORAWAN_NVM_NONE)) { + macCallbacks.NvmDataChange = NULL; + } else { + macCallbacks.NvmDataChange = lorawan_nvm_data_mgmt_event; + } + macCallbacks.MacProcessNotify = OnMacProcessNotify; status = LoRaMacInitialization(&macPrimitives, &macCallbacks, @@ -605,9 +618,13 @@ static int lorawan_init(const struct device *dev) return -EINVAL; } + if (!IS_ENABLED(CONFIG_LORAWAN_NVM_NONE)) { + lorawan_nvm_init(); + lorawan_nvm_data_restore(); + } + LOG_DBG("LoRaMAC Initialized"); return 0; } - SYS_INIT(lorawan_init, APPLICATION, CONFIG_KERNEL_INIT_PRIORITY_DEVICE); diff --git a/subsys/lorawan/nvm/CMakeLists.txt b/subsys/lorawan/nvm/CMakeLists.txt new file mode 100644 index 00000000000..2b7b710690e --- /dev/null +++ b/subsys/lorawan/nvm/CMakeLists.txt @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: Apache-2.0 + +zephyr_library_sources_ifdef(CONFIG_LORAWAN_NVM_SETTINGS lorawan_nvm_settings.c) diff --git a/subsys/lorawan/nvm/Kconfig b/subsys/lorawan/nvm/Kconfig new file mode 100644 index 00000000000..da847d829f3 --- /dev/null +++ b/subsys/lorawan/nvm/Kconfig @@ -0,0 +1,23 @@ +# LoRaWAN Non Volatile Memory configuration options + +# Copyright (c) 2022 Intellinium +# SPDX-License-Identifier: Apache-2.0 + +choice LORAWAN_NVM + bool "LoRaWAN NVM backend" + default LORAWAN_NVM_SETTINGS if SETTINGS + default LORAWAN_NVM_NONE + +config LORAWAN_NVM_NONE + bool "No NVM backend for LoRaWAN" + help + If this configuration is used, it is the responsibility of the + application to store and restore the DevNonce value each time + a OTAA join request is sent. This value should be used in the + join configuration directly. + +config LORAWAN_NVM_SETTINGS + bool "Settings based NVM" + depends on SETTINGS + +endchoice diff --git a/subsys/lorawan/nvm/lorawan_nvm.h b/subsys/lorawan/nvm/lorawan_nvm.h new file mode 100644 index 00000000000..92c34d69775 --- /dev/null +++ b/subsys/lorawan/nvm/lorawan_nvm.h @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2022 Intellinium + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_SUBSYS_LORAWAN_NVM_H_ +#define ZEPHYR_SUBSYS_LORAWAN_NVM_H_ + +#include + +/** + * @brief Hook function called when an interrupt related to NVM + * is received from the LoRaWAN stack. + * + * @note This function should not be called directly by the application + * + * @param flags OR'ed flags received with the interrupt + * + * @see LORAMAC_NVM_NOTIFY_FLAG_NONE + * @see LORAMAC_NVM_NOTIFY_FLAG_CRYPTO + * @see LORAMAC_NVM_NOTIFY_FLAG_MAC_GROUP1 + * @see LORAMAC_NVM_NOTIFY_FLAG_MAC_GROUP2 + * @see LORAMAC_NVM_NOTIFY_FLAG_SECURE_ELEMENT + * @see LORAMAC_NVM_NOTIFY_FLAG_REGION_GROUP1 + * @see LORAMAC_NVM_NOTIFY_FLAG_REGION_GROUP2 + * @see LORAMAC_NVM_NOTIFY_FLAG_CLASS_B + */ +void lorawan_nvm_data_mgmt_event(uint16_t flags); + +/** + * @brief Restores all the relevant LoRaWAN data from the Non-Volatile Memory. + * + * @note This function should only be called if a NVM has been enabled, and + * not directly by the application. + * + * @return 0 on success, or a negative error code otherwise + */ +int lorawan_nvm_data_restore(void); + +/** + * @brief Initializes the NVM backend + * + * @note This function shall be called before any other + * NVM back functions. + * + * @return 0 on success, or a negative error code otherwise + */ +int lorawan_nvm_init(void); + +#endif /* ZEPHYR_SUBSYS_LORAWAN_NVM_H_ */ diff --git a/subsys/lorawan/nvm/lorawan_nvm_settings.c b/subsys/lorawan/nvm/lorawan_nvm_settings.c new file mode 100644 index 00000000000..127bcad8ca1 --- /dev/null +++ b/subsys/lorawan/nvm/lorawan_nvm_settings.c @@ -0,0 +1,186 @@ +/* + * Copyright (c) 2022 Intellinium + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include "lorawan_nvm.h" +#include + +LOG_MODULE_REGISTER(lorawan_nvm, CONFIG_LORAWAN_LOG_LEVEL); + +#define LORAWAN_SETTINGS_BASE "lorawan/nvm" + +struct lorawan_nvm_setting_descr { + const char *name; + const char *setting_name; + size_t size; + off_t offset; + uint16_t flag; +}; + +#define NVM_SETTING_DESCR(_flag, _member) \ + { \ + .flag = _flag, \ + .name = STRINGIFY(_member), \ + .setting_name = \ + LORAWAN_SETTINGS_BASE "/" STRINGIFY(_member), \ + .offset = offsetof(LoRaMacNvmData_t, _member), \ + .size = sizeof(((LoRaMacNvmData_t *)0)->_member), \ + } + +static const struct lorawan_nvm_setting_descr nvm_setting_descriptors[] = { + NVM_SETTING_DESCR(LORAMAC_NVM_NOTIFY_FLAG_CRYPTO, Crypto), + NVM_SETTING_DESCR(LORAMAC_NVM_NOTIFY_FLAG_MAC_GROUP1, MacGroup1), + NVM_SETTING_DESCR(LORAMAC_NVM_NOTIFY_FLAG_MAC_GROUP2, MacGroup2), + NVM_SETTING_DESCR(LORAMAC_NVM_NOTIFY_FLAG_SECURE_ELEMENT, SecureElement), + NVM_SETTING_DESCR(LORAMAC_NVM_NOTIFY_FLAG_REGION_GROUP1, RegionGroup1), + NVM_SETTING_DESCR(LORAMAC_NVM_NOTIFY_FLAG_REGION_GROUP2, RegionGroup2), + NVM_SETTING_DESCR(LORAMAC_NVM_NOTIFY_FLAG_CLASS_B, ClassB), +}; + +static void lorawan_nvm_save_settings(uint16_t nvm_notify_flag) +{ + MibRequestConfirm_t mib_req; + + LOG_DBG("Saving LoRaWAN settings"); + + /* Retrieve the actual context */ + mib_req.Type = MIB_NVM_CTXS; + if (LoRaMacMibGetRequestConfirm(&mib_req) != LORAMAC_STATUS_OK) { + LOG_ERR("Could not get NVM context"); + return; + } + + LoRaMacNvmData_t *nvm = mib_req.Param.Contexts; + + LOG_DBG("Crypto version: %"PRIu32", DevNonce: %d, JoinNonce: %"PRIu32, + mib_req.Param.Contexts->Crypto.LrWanVersion.Value, + mib_req.Param.Contexts->Crypto.DevNonce, + mib_req.Param.Contexts->Crypto.JoinNonce); + + for (uint32_t i = 0; i < ARRAY_SIZE(nvm_setting_descriptors); i++) { + const struct lorawan_nvm_setting_descr *descr = + &nvm_setting_descriptors[i]; + + if ((nvm_notify_flag & descr->flag) == descr->flag) { + LOG_DBG("Saving configuration %s", descr->setting_name); + int err = settings_save_one(descr->setting_name, + (char *)nvm + descr->offset, + descr->size); + if (err) { + LOG_ERR("Could not save settings %s, error %d", + descr->name, err); + } + } + } + + settings_save(); +} + +void lorawan_nvm_data_mgmt_event(uint16_t flags) +{ + if (flags != LORAMAC_NVM_NOTIFY_FLAG_NONE) { + lorawan_nvm_save_settings(flags); + } +} + +static int load_setting(void *tgt, size_t tgt_size, + const char *key, size_t len, + settings_read_cb read_cb, void *cb_arg) +{ + if (len != tgt_size) { + LOG_ERR("Can't load '%s' state, size mismatch.", + log_strdup(key)); + return -EINVAL; + } + + if (!tgt) { + LOG_ERR("Can't load '%s' state, no target.", + log_strdup(key)); + return -EINVAL; + } + + if (read_cb(cb_arg, tgt, len) != len) { + LOG_ERR("Can't load '%s' state, short read.", + log_strdup(key)); + return -EINVAL; + } + + return 0; +} + +static int on_setting_loaded(const char *key, size_t len, + settings_read_cb read_cb, + void *cb_arg, void *param) +{ + int err; + LoRaMacNvmData_t *nvm = param; + + LOG_DBG("Key: %s", log_strdup(key)); + + for (uint32_t i = 0; i < ARRAY_SIZE(nvm_setting_descriptors); i++) { + const struct lorawan_nvm_setting_descr *descr = + &nvm_setting_descriptors[i]; + + if (strcmp(descr->name, key) == 0) { + err = load_setting((char *)nvm + descr->offset, + descr->size, key, len, read_cb, cb_arg); + if (err) { + LOG_ERR("Could not read setting %s", descr->name); + } + return err; + } + } + + LOG_WRN("Unknown LoRaWAN setting: %s", log_strdup(key)); + return 0; +} + +int lorawan_nvm_data_restore(void) +{ + int err; + LoRaMacStatus_t status; + MibRequestConfirm_t mib_req; + + LOG_DBG("Restoring LoRaWAN settings"); + + /* Retrieve the actual context */ + mib_req.Type = MIB_NVM_CTXS; + if (LoRaMacMibGetRequestConfirm(&mib_req) != LORAMAC_STATUS_OK) { + LOG_ERR("Could not get NVM context"); + return -EINVAL; + } + + err = settings_load_subtree_direct(LORAWAN_SETTINGS_BASE, + on_setting_loaded, + mib_req.Param.Contexts); + if (err) { + LOG_ERR("Could not load LoRaWAN settings, error %d", err); + return err; + } + + LOG_DBG("Crypto version: %"PRIu32", DevNonce: %d, JoinNonce: %"PRIu32, + mib_req.Param.Contexts->Crypto.LrWanVersion.Value, + mib_req.Param.Contexts->Crypto.DevNonce, + mib_req.Param.Contexts->Crypto.JoinNonce); + + mib_req.Type = MIB_NVM_CTXS; + status = LoRaMacMibSetRequestConfirm(&mib_req); + if (status != LORAMAC_STATUS_OK) { + LOG_ERR("Could not set the NVM context, error %d", status); + return -EINVAL; + } + + LOG_DBG("LoRaWAN context restored"); + + return 0; +} + +int lorawan_nvm_init(void) +{ + return settings_subsys_init(); +}