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 <giuliano.franchetto@intellinium.com>
This commit is contained in:
Giuliano Franchetto 2022-01-03 12:32:18 +01:00 committed by Carles Cufí
commit 6630f7fc07
7 changed files with 305 additions and 22 deletions

View file

@ -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)

View file

@ -66,4 +66,6 @@ config LORAMAC_REGION_RU864
endchoice
rsource "nvm/Kconfig"
endif # LORAWAN

View file

@ -7,38 +7,38 @@
#include <init.h>
#include <errno.h>
#include <lorawan/lorawan.h>
#include <zephyr.h>
#include "lw_priv.h"
#include <LoRaMac.h>
#include <Region.h>
#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);

View file

@ -0,0 +1,3 @@
# SPDX-License-Identifier: Apache-2.0
zephyr_library_sources_ifdef(CONFIG_LORAWAN_NVM_SETTINGS lorawan_nvm_settings.c)

View file

@ -0,0 +1,23 @@
# LoRaWAN Non Volatile Memory configuration options
# Copyright (c) 2022 Intellinium <giuliano.franchetto@intellinium.com>
# 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

View file

@ -0,0 +1,51 @@
/*
* Copyright (c) 2022 Intellinium <giuliano.franchetto@intellinium.com>
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef ZEPHYR_SUBSYS_LORAWAN_NVM_H_
#define ZEPHYR_SUBSYS_LORAWAN_NVM_H_
#include <stdint.h>
/**
* @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_ */

View file

@ -0,0 +1,186 @@
/*
* Copyright (c) 2022 Intellinium <giuliano.franchetto@intellinium.com>
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <LoRaMac.h>
#include <kernel.h>
#include <settings/settings.h>
#include "lorawan_nvm.h"
#include <logging/log.h>
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();
}