zephyr/subsys/bluetooth/mesh/settings.c
Pavel Vasilyev e33a4ace0f Bluetooth: Mesh: Use separate workq for storing mesh settings
Currently mesh settings are stored in the system workqueue context.
Most of other stack functionality, such that advertisements (incl
relay), loopback, transport sar, beacons transmission, etc. is also
processed in the system workqueue context. When a massive amount of
data needs to be stored and in particularly when page erase needs to
be triggered by GC of NVS subsystem to allocate flash pages, the
execution of the stack (and other functionality that uses the system
workqueue) will be blocked until storing is finished. For example,
right after the provisioning of a erased device, a node may not be
responsive for up to 400ms before it can continue sending messages.
The waiting time may increase if there is a GATT connection in the
mean time.

When write or erase operation is triggered, the flash driver waits for
Bluetooth controller to allocate a time needed to perform the operation.
During the whole operation, the context from which the operation was
triggered is put to sleep. This allows other threads to run until
Bluetooth controller finds the time for the flash driver. In other words,
every settings_save_one or settings_delete should be considered as
rescheduling points.

Considering this, Bluetooth mesh can use another thread to store its
settings, thus releasing the system workqueue for other tasks including
the operation of the stack itself.

The consistency of the data to be stored is guaranteed by the current
implementation where the data is copied to another struct before calling
settings_save_one. The pending flag of a particular module is dropped in
settings.c before starting to store the corresponding data. Thus, if
during the sleep the node receives a message that triggers a change in a
module which data is currently being stored, the pending flag will be
restored and the new change will be stored eventually.

Having this option enabled including with the partial erase, will make
the node more responsive in the described situations.

Signed-off-by: Pavel Vasilyev <pavel.vasilyev@nordicsemi.no>
2023-05-08 09:58:21 +02:00

284 lines
7.7 KiB
C

/*
* Copyright (c) 2018 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/kernel.h>
#include <errno.h>
#include <sys/types.h>
#include <zephyr/sys/util.h>
#include <zephyr/bluetooth/hci.h>
#include <zephyr/settings/settings.h>
#include "host/hci_core.h"
#include "mesh.h"
#include "subnet.h"
#include "app_keys.h"
#include "net.h"
#include "cdb.h"
#include "crypto.h"
#include "rpl.h"
#include "transport.h"
#include "heartbeat.h"
#include "access.h"
#include "proxy.h"
#include "pb_gatt_srv.h"
#include "settings.h"
#include "cfg.h"
#include "solicitation.h"
#define LOG_LEVEL CONFIG_BT_MESH_SETTINGS_LOG_LEVEL
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(bt_mesh_settings);
#ifdef CONFIG_BT_MESH_RPL_STORE_TIMEOUT
#define RPL_STORE_TIMEOUT CONFIG_BT_MESH_RPL_STORE_TIMEOUT
#else
#define RPL_STORE_TIMEOUT (-1)
#endif
#ifdef CONFIG_BT_MESH_SETTINGS_WORKQ_PRIO
#define SETTINGS_WORKQ_PRIO CONFIG_BT_MESH_SETTINGS_WORKQ_PRIO
#else
#define SETTINGS_WORKQ_PRIO 1
#endif
#ifdef CONFIG_BT_MESH_SETTINGS_WORKQ_STACK_SIZE
#define SETTINGS_WORKQ_STACK_SIZE CONFIG_BT_MESH_SETTINGS_WORKQ_STACK_SIZE
#else
#define SETTINGS_WORKQ_STACK_SIZE 0
#endif
static struct k_work_q settings_work_q;
static K_THREAD_STACK_DEFINE(settings_work_stack, SETTINGS_WORKQ_STACK_SIZE);
static struct k_work_delayable pending_store;
static ATOMIC_DEFINE(pending_flags, BT_MESH_SETTINGS_FLAG_COUNT);
int bt_mesh_settings_set(settings_read_cb read_cb, void *cb_arg,
void *out, size_t read_len)
{
ssize_t len;
len = read_cb(cb_arg, out, read_len);
if (len < 0) {
LOG_ERR("Failed to read value (err %zd)", len);
return len;
}
LOG_HEXDUMP_DBG(out, len, "val");
if (len != read_len) {
LOG_ERR("Unexpected value length (%zd != %zu)", len, read_len);
return -EINVAL;
}
return 0;
}
static int mesh_commit(void)
{
if (!atomic_test_bit(bt_mesh.flags, BT_MESH_INIT)) {
return 0;
}
if (!atomic_test_bit(bt_dev.flags, BT_DEV_ENABLE)) {
/* The Bluetooth mesh settings loader calls bt_mesh_start() immediately
* after loading the settings. This is not intended to work before
* bt_enable(). The doc on @ref bt_enable requires the "bt/" settings
* tree to be loaded after @ref bt_enable is completed, so this handler
* will be called again later.
*/
return 0;
}
if (!bt_mesh_subnet_next(NULL)) {
/* Nothing to do since we're not yet provisioned */
return 0;
}
if (IS_ENABLED(CONFIG_BT_MESH_PB_GATT)) {
(void)bt_mesh_pb_gatt_srv_disable();
}
bt_mesh_net_settings_commit();
bt_mesh_model_settings_commit();
atomic_set_bit(bt_mesh.flags, BT_MESH_VALID);
bt_mesh_start();
return 0;
}
SETTINGS_STATIC_HANDLER_DEFINE(bt_mesh, "bt/mesh", NULL, NULL, mesh_commit,
NULL);
/* Pending flags that use K_NO_WAIT as the storage timeout */
#define NO_WAIT_PENDING_BITS (BIT(BT_MESH_SETTINGS_NET_PENDING) | \
BIT(BT_MESH_SETTINGS_IV_PENDING) | \
BIT(BT_MESH_SETTINGS_SEQ_PENDING) | \
BIT(BT_MESH_SETTINGS_CDB_PENDING))
/* Pending flags that use CONFIG_BT_MESH_STORE_TIMEOUT */
#define GENERIC_PENDING_BITS (BIT(BT_MESH_SETTINGS_NET_KEYS_PENDING) | \
BIT(BT_MESH_SETTINGS_APP_KEYS_PENDING) | \
BIT(BT_MESH_SETTINGS_HB_PUB_PENDING) | \
BIT(BT_MESH_SETTINGS_CFG_PENDING) | \
BIT(BT_MESH_SETTINGS_MOD_PENDING) | \
BIT(BT_MESH_SETTINGS_VA_PENDING) | \
BIT(BT_MESH_SETTINGS_SSEQ_PENDING) | \
BIT(BT_MESH_SETTINGS_COMP_PENDING) | \
BIT(BT_MESH_SETTINGS_DEV_KEY_CAND_PENDING))
void bt_mesh_settings_store_schedule(enum bt_mesh_settings_flag flag)
{
uint32_t timeout_ms, remaining_ms;
atomic_set_bit(pending_flags, flag);
if (atomic_get(pending_flags) & NO_WAIT_PENDING_BITS) {
timeout_ms = 0;
} else if (IS_ENABLED(CONFIG_BT_MESH_RPL_STORAGE_MODE_SETTINGS) && RPL_STORE_TIMEOUT >= 0 &&
(atomic_test_bit(pending_flags, BT_MESH_SETTINGS_RPL_PENDING) ||
atomic_test_bit(pending_flags, BT_MESH_SETTINGS_SRPL_PENDING)) &&
!(atomic_get(pending_flags) & GENERIC_PENDING_BITS)) {
timeout_ms = RPL_STORE_TIMEOUT * MSEC_PER_SEC;
} else {
timeout_ms = CONFIG_BT_MESH_STORE_TIMEOUT * MSEC_PER_SEC;
}
remaining_ms = k_ticks_to_ms_floor32(k_work_delayable_remaining_get(&pending_store));
LOG_DBG("Waiting %u ms vs rem %u ms", timeout_ms, remaining_ms);
/* If the new deadline is sooner, override any existing
* deadline; otherwise schedule without changing any existing
* deadline.
*/
if (timeout_ms < remaining_ms) {
if (IS_ENABLED(CONFIG_BT_MESH_SETTINGS_WORKQ)) {
k_work_reschedule_for_queue(&settings_work_q, &pending_store,
K_MSEC(timeout_ms));
} else {
k_work_reschedule(&pending_store, K_MSEC(timeout_ms));
}
} else {
if (IS_ENABLED(CONFIG_BT_MESH_SETTINGS_WORKQ)) {
k_work_schedule_for_queue(&settings_work_q, &pending_store,
K_MSEC(timeout_ms));
} else {
k_work_schedule(&pending_store, K_MSEC(timeout_ms));
}
}
}
void bt_mesh_settings_store_cancel(enum bt_mesh_settings_flag flag)
{
atomic_clear_bit(pending_flags, flag);
}
static void store_pending(struct k_work *work)
{
LOG_DBG("");
if (IS_ENABLED(CONFIG_BT_MESH_RPL_STORAGE_MODE_SETTINGS) &&
atomic_test_and_clear_bit(pending_flags, BT_MESH_SETTINGS_RPL_PENDING)) {
bt_mesh_rpl_pending_store(BT_MESH_ADDR_ALL_NODES);
}
if (atomic_test_and_clear_bit(pending_flags,
BT_MESH_SETTINGS_NET_KEYS_PENDING)) {
bt_mesh_subnet_pending_store();
}
if (atomic_test_and_clear_bit(pending_flags,
BT_MESH_SETTINGS_APP_KEYS_PENDING)) {
bt_mesh_app_key_pending_store();
}
if (atomic_test_and_clear_bit(pending_flags,
BT_MESH_SETTINGS_NET_PENDING)) {
bt_mesh_net_pending_net_store();
}
if (atomic_test_and_clear_bit(pending_flags,
BT_MESH_SETTINGS_IV_PENDING)) {
bt_mesh_net_pending_iv_store();
}
if (atomic_test_and_clear_bit(pending_flags,
BT_MESH_SETTINGS_SEQ_PENDING)) {
bt_mesh_net_pending_seq_store();
}
if (atomic_test_and_clear_bit(pending_flags,
BT_MESH_SETTINGS_DEV_KEY_CAND_PENDING)) {
bt_mesh_net_pending_dev_key_cand_store();
}
if (atomic_test_and_clear_bit(pending_flags,
BT_MESH_SETTINGS_HB_PUB_PENDING)) {
bt_mesh_hb_pub_pending_store();
}
if (atomic_test_and_clear_bit(pending_flags,
BT_MESH_SETTINGS_CFG_PENDING)) {
bt_mesh_cfg_pending_store();
}
if (atomic_test_and_clear_bit(pending_flags,
BT_MESH_SETTINGS_COMP_PENDING)) {
bt_mesh_comp_data_pending_clear();
}
if (atomic_test_and_clear_bit(pending_flags,
BT_MESH_SETTINGS_MOD_PENDING)) {
bt_mesh_model_pending_store();
}
if (atomic_test_and_clear_bit(pending_flags,
BT_MESH_SETTINGS_VA_PENDING)) {
bt_mesh_va_pending_store();
}
if (IS_ENABLED(CONFIG_BT_MESH_CDB) &&
atomic_test_and_clear_bit(pending_flags,
BT_MESH_SETTINGS_CDB_PENDING)) {
bt_mesh_cdb_pending_store();
}
if (IS_ENABLED(CONFIG_BT_MESH_OD_PRIV_PROXY_SRV) &&
atomic_test_and_clear_bit(pending_flags,
BT_MESH_SETTINGS_SRPL_PENDING)) {
bt_mesh_srpl_pending_store();
}
if (IS_ENABLED(CONFIG_BT_MESH_PROXY_SOLICITATION) &&
atomic_test_and_clear_bit(pending_flags,
BT_MESH_SETTINGS_SSEQ_PENDING)) {
bt_mesh_sseq_pending_store();
}
}
void bt_mesh_settings_init(void)
{
if (IS_ENABLED(CONFIG_BT_MESH_SETTINGS_WORKQ)) {
k_work_queue_start(&settings_work_q, settings_work_stack,
K_THREAD_STACK_SIZEOF(settings_work_stack),
K_PRIO_COOP(SETTINGS_WORKQ_PRIO), NULL);
k_thread_name_set(&settings_work_q.thread, "BT Mesh settings workq");
}
k_work_init_delayable(&pending_store, store_pending);
}
void bt_mesh_settings_store_pending(void)
{
(void)k_work_cancel_delayable(&pending_store);
store_pending(&pending_store.work);
}