Bluetooth: settings: store CCC on write

By default, CCC value is only stored to persistent memory during
BT disconnection. This commit adds an optional storing of CCC right
after it has been updated. This results in better robustness of
peripheral but increases system workqueue stack usage.

Signed-off-by: Filip Kubicz <filip.kubicz@nordicsemi.no>
This commit is contained in:
Filip Kubicz 2018-10-08 14:33:40 +02:00 committed by Johan Hedberg
parent 47f529829f
commit 7c290b053d
2 changed files with 94 additions and 2 deletions

View file

@ -101,7 +101,7 @@ config BT_RX_STACK_SIZE
depends on BT_HCI_HOST || BT_RECV_IS_RX_THREAD
default 512 if BT_HCI_RAW
default 2048 if BT_MESH
default 2048 if BT_SETTINGS
default 2200 if BT_SETTINGS
default 1024
range 512 65536 if BT_HCI_RAW
range 1100 65536 if BT_MESH
@ -153,6 +153,20 @@ config BT_SETTINGS
which case it's more efficient to load all settings in one go,
instead of each subsystem doing it independently.
if BT_SETTINGS
config BT_SETTINGS_CCC_STORE_ON_WRITE
bool "Store CCC value immediately after it has been written"
default n
help
Store Client Configuration Characteristic value right after it has
been updated.
By default, CCC is only stored on disconnection.
Choosing this option is safer for battery-powered devices or devices
that expect to be reset suddenly. However, it requires additional
workqueue stack space.
endif # BT_SETTINGS
if BT_CONN
if BT_HCI_ACL_FLOW_CONTROL

View file

@ -36,7 +36,8 @@
#include "settings.h"
#include "gatt_internal.h"
#define SC_TIMEOUT K_MSEC(10)
#define SC_TIMEOUT K_MSEC(10)
#define CCC_STORE_DELAY K_SECONDS(1)
/* Persistent storage format for GATT CCC */
struct ccc_store {
@ -275,6 +276,59 @@ static void sc_process(struct k_work *work)
atomic_set_bit(sc->flags, SC_INDICATE_PENDING);
}
#if defined(CONFIG_BT_SETTINGS_CCC_STORE_ON_WRITE)
static struct gatt_ccc_store {
struct bt_conn *conn_list[CONFIG_BT_MAX_CONN];
struct k_delayed_work work;
} gatt_ccc_store;
static bool gatt_ccc_conn_is_queued(struct bt_conn *conn)
{
return (conn == gatt_ccc_store.conn_list[bt_conn_get_id(conn)]);
}
static void gatt_ccc_conn_unqueue(struct bt_conn *conn)
{
u8_t index = bt_conn_get_id(conn);
if (gatt_ccc_store.conn_list[index] != NULL) {
bt_conn_unref(gatt_ccc_store.conn_list[index]);
gatt_ccc_store.conn_list[index] = NULL;
}
}
static bool gatt_ccc_conn_queue_is_empty(void)
{
for (size_t i = 0; i < CONFIG_BT_MAX_CONN; i++) {
if (gatt_ccc_store.conn_list[i]) {
return false;
}
}
return true;
}
static void ccc_delayed_store(struct k_work *work)
{
struct gatt_ccc_store *ccc_store =
CONTAINER_OF(work, struct gatt_ccc_store, work);
for (size_t i = 0; i < CONFIG_BT_MAX_CONN; i++) {
struct bt_conn *conn = ccc_store->conn_list[i];
if (!conn) {
continue;
}
if (bt_addr_le_is_bonded(conn->id, &conn->le.dst)) {
bt_gatt_store_ccc(conn->id, &conn->le.dst);
bt_conn_unref(conn);
ccc_store->conn_list[i] = NULL;
}
}
}
#endif
void bt_gatt_init(void)
{
if (!atomic_cas(&init, 0, 1)) {
@ -286,6 +340,9 @@ void bt_gatt_init(void)
gatt_register(&gatt_svc);
k_delayed_work_init(&gatt_sc.work, sc_process);
#if defined(CONFIG_BT_SETTINGS_CCC_STORE_ON_WRITE)
k_delayed_work_init(&gatt_ccc_store.work, ccc_delayed_store);
#endif
}
static bool update_range(u16_t *start, u16_t *end, u16_t new_start,
@ -658,6 +715,19 @@ ssize_t bt_gatt_attr_write_ccc(struct bt_conn *conn,
/* Update cfg if don't match */
if (ccc->cfg[i].value != ccc->value) {
gatt_ccc_changed(attr, ccc);
#if defined(CONFIG_BT_SETTINGS_CCC_STORE_ON_WRITE)
if ((!gatt_ccc_conn_is_queued(conn)) &&
bt_addr_le_is_bonded(conn->id, &conn->le.dst)) {
/* Store the connection with the same index it has in
* the conns array
*/
gatt_ccc_store.conn_list[bt_conn_get_id(conn)] =
bt_conn_ref(conn);
k_delayed_work_submit(&gatt_ccc_store.work,
CCC_STORE_DELAY);
}
#endif
}
/* Disabled CCC is the same as no configured CCC, so clear the entry */
@ -2365,6 +2435,14 @@ void bt_gatt_disconnected(struct bt_conn *conn)
BT_DBG("conn %p", conn);
bt_gatt_foreach_attr(0x0001, 0xffff, disconnected_cb, conn);
#if defined(CONFIG_BT_SETTINGS_CCC_STORE_ON_WRITE)
gatt_ccc_conn_unqueue(conn);
if (gatt_ccc_conn_queue_is_empty()) {
k_delayed_work_cancel(&gatt_ccc_store.work);
}
#endif
if (IS_ENABLED(CONFIG_BT_SETTINGS) &&
bt_addr_le_is_bonded(conn->id, &conn->le.dst)) {
bt_gatt_store_ccc(conn->id, &conn->le.dst);