blob: 097d5abcc82238b19635f9f872d58499617906ef [file] [log] [blame]
/*
* Copyright (c) 2020 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/settings/settings.h>
#include "dfu_slot.h"
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <zephyr/sys/util.h>
#include <common/bt_str.h>
#define LOG_LEVEL CONFIG_BT_MESH_DFU_LOG_LEVEL
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(bt_mesh_dfu_slot);
#define SLOT_ENTRY_BUFLEN 25
#define DFU_SLOT_SETTINGS_PATH "bt/mesh-dfu/slot"
#define HEADER_SIZE offsetof(struct slot, slot.fwid)
#define PROP_HEADER "h"
#define PROP_FWID "id"
#define PROP_METADATA "m"
static sys_slist_t list;
static struct slot {
uint32_t idx;
struct bt_mesh_dfu_slot slot;
sys_snode_t n;
} slots[CONFIG_BT_MESH_DFU_SLOT_CNT];
static uint32_t slot_index;
static char *slot_entry_encode(uint16_t idx, char buf[SLOT_ENTRY_BUFLEN],
const char *property)
{
snprintf(buf, SLOT_ENTRY_BUFLEN, DFU_SLOT_SETTINGS_PATH "/%x/%s", idx,
property);
return buf;
}
static bool slot_eq(const struct bt_mesh_dfu_slot *slot,
const uint8_t *fwid, size_t fwid_len)
{
return (slot->fwid_len == fwid_len) &&
!memcmp(fwid, slot->fwid, fwid_len);
}
static bool is_slot_committed(struct slot *slot_to_check)
{
struct slot *s;
SYS_SLIST_FOR_EACH_CONTAINER(&list, s, n) {
if (s == slot_to_check) {
return true;
}
}
return false;
}
static int slot_store(const struct slot *slot_to_store)
{
uint16_t idx = ARRAY_INDEX(slots, slot_to_store);
char buf[SLOT_ENTRY_BUFLEN];
int err;
err = settings_save_one(slot_entry_encode(idx, buf, PROP_HEADER),
slot_to_store, HEADER_SIZE);
if (err) {
return err;
}
err = settings_save_one(slot_entry_encode(idx, buf, PROP_FWID),
slot_to_store->slot.fwid, slot_to_store->slot.fwid_len);
if (err) {
return err;
}
err = settings_save_one(slot_entry_encode(idx, buf,
PROP_METADATA),
slot_to_store->slot.metadata, slot_to_store->slot.metadata_len);
return err;
}
static void slot_erase(struct slot *slot_to_erase)
{
uint16_t idx = ARRAY_INDEX(slots, slot_to_erase);
char buf[SLOT_ENTRY_BUFLEN];
settings_delete(slot_entry_encode(idx, buf, PROP_HEADER));
settings_delete(slot_entry_encode(idx, buf, PROP_FWID));
settings_delete(slot_entry_encode(idx, buf, PROP_METADATA));
}
static void slot_index_defrag(void)
{
slot_index = 0;
struct slot *s;
SYS_SLIST_FOR_EACH_CONTAINER(&list, s, n) {
s->idx = ++slot_index;
slot_store(s);
}
}
int bt_mesh_dfu_slot_count(void)
{
int cnt = 0;
sys_snode_t *n;
SYS_SLIST_FOR_EACH_NODE(&list, n) {
cnt++;
}
return cnt;
}
struct bt_mesh_dfu_slot *bt_mesh_dfu_slot_reserve(void)
{
struct slot *slot = NULL;
for (int i = 0; i < ARRAY_SIZE(slots); ++i) {
if (slots[i].idx == 0) {
slot = &slots[i];
break;
}
}
if (!slot) {
LOG_WRN("No space");
return NULL;
}
if (slot_index == UINT32_MAX) {
slot_index_defrag();
}
slot->slot.fwid_len = 0;
slot->slot.metadata_len = 0;
slot->slot.size = 0;
slot->idx = ++slot_index;
LOG_DBG("Reserved slot #%u", slot - &slots[0]);
return &slot->slot;
}
int bt_mesh_dfu_slot_info_set(struct bt_mesh_dfu_slot *dfu_slot, size_t size,
const uint8_t *metadata, size_t metadata_len)
{
struct slot *slot = CONTAINER_OF(dfu_slot, struct slot, slot);
if (metadata_len > CONFIG_BT_MESH_DFU_METADATA_MAXLEN) {
return -EFBIG;
}
if (slot->idx == 0 || is_slot_committed(slot)) {
return -EINVAL;
}
slot->slot.size = size;
slot->slot.metadata_len = metadata_len;
memcpy(slot->slot.metadata, metadata, metadata_len);
return 0;
}
int bt_mesh_dfu_slot_fwid_set(struct bt_mesh_dfu_slot *dfu_slot,
const uint8_t *fwid, size_t fwid_len)
{
struct slot *slot = CONTAINER_OF(dfu_slot, struct slot, slot);
if (fwid_len > CONFIG_BT_MESH_DFU_FWID_MAXLEN) {
return -EFBIG;
}
if (slot->idx == 0 || is_slot_committed(slot)) {
return -EINVAL;
}
for (int i = 0; i < ARRAY_SIZE(slots); i++) {
if (slots[i].idx != 0 &&
slot_eq(&slots[i].slot, fwid, fwid_len)) {
return is_slot_committed(&slots[i]) ?
-EEXIST : -EALREADY;
}
}
slot->slot.fwid_len = fwid_len;
memcpy(slot->slot.fwid, fwid, fwid_len);
return 0;
}
int bt_mesh_dfu_slot_commit(struct bt_mesh_dfu_slot *dfu_slot)
{
int err;
struct slot *slot = CONTAINER_OF(dfu_slot, struct slot, slot);
if (slot->idx == 0 ||
slot->slot.fwid_len == 0 ||
slot->slot.size == 0 ||
is_slot_committed(slot)) {
return -EINVAL;
}
err = slot_store(slot);
if (err) {
LOG_WRN("Store failed (err: %d)", err);
return err;
}
sys_slist_append(&list, &slot->n);
LOG_DBG("Stored slot #%u: %s", ARRAY_INDEX(slots, slot),
bt_hex(slot->slot.fwid, slot->slot.fwid_len));
return 0;
}
void bt_mesh_dfu_slot_release(const struct bt_mesh_dfu_slot *dfu_slot)
{
struct slot *slot = CONTAINER_OF(dfu_slot, struct slot, slot);
if (is_slot_committed(slot)) {
return;
}
slot->idx = 0;
}
int bt_mesh_dfu_slot_del(const struct bt_mesh_dfu_slot *dfu_slot)
{
struct slot *slot = CONTAINER_OF(dfu_slot, struct slot, slot);
if (!sys_slist_find_and_remove(&list, &slot->n)) {
return -EINVAL;
}
int idx = ARRAY_INDEX(slots, slot);
LOG_DBG("%u", idx);
slot_erase(slot);
slot->idx = 0;
return 0;
}
void bt_mesh_dfu_slot_del_all(void)
{
struct slot *s;
SYS_SLIST_FOR_EACH_CONTAINER(&list, s, n) {
slot_erase(s);
s->idx = 0;
}
sys_slist_init(&list);
}
const struct bt_mesh_dfu_slot *bt_mesh_dfu_slot_at(uint16_t img_idx)
{
struct slot *s;
SYS_SLIST_FOR_EACH_CONTAINER(&list, s, n) {
if (!img_idx--) {
return &s->slot;
}
}
return NULL;
}
int bt_mesh_dfu_slot_get(const uint8_t *fwid, size_t fwid_len, struct bt_mesh_dfu_slot **slot)
{
struct slot *s;
int idx = 0;
SYS_SLIST_FOR_EACH_CONTAINER(&list, s, n) {
if (slot_eq(&s->slot, fwid, fwid_len)) {
if (slot) {
*slot = &s->slot;
}
return idx;
}
idx++;
}
return -ENOENT;
}
int bt_mesh_dfu_slot_img_idx_get(const struct bt_mesh_dfu_slot *dfu_slot)
{
struct slot *s;
int idx = 0;
SYS_SLIST_FOR_EACH_CONTAINER(&list, s, n) {
if (&s->slot == dfu_slot) {
return idx;
}
idx++;
}
return -ENOENT;
}
size_t bt_mesh_dfu_slot_foreach(bt_mesh_dfu_slot_cb_t cb, void *user_data)
{
enum bt_mesh_dfu_iter iter;
size_t cnt = 0;
struct slot *s;
SYS_SLIST_FOR_EACH_CONTAINER(&list, s, n) {
cnt++;
if (!cb) {
continue;
}
iter = cb(&s->slot, user_data);
if (iter != BT_MESH_DFU_ITER_CONTINUE) {
break;
}
}
return cnt;
}
static int slot_data_load(const char *key, size_t len_rd,
settings_read_cb read_cb, void *cb_arg)
{
const char *prop;
size_t len;
uint16_t idx;
idx = strtol(key, NULL, 16);
if (idx >= ARRAY_SIZE(slots)) {
return 0;
}
len = settings_name_next(key, &prop);
if (!strncmp(prop, PROP_HEADER, len)) {
if (read_cb(cb_arg, &slots[idx], HEADER_SIZE) > 0) {
struct slot *s, *prev = NULL;
SYS_SLIST_FOR_EACH_CONTAINER(&list, s, n) {
if (s->idx > slots[idx].idx) {
break;
}
prev = s;
}
if (prev == NULL) {
sys_slist_prepend(&list, &slots[idx].n);
} else {
sys_slist_insert(&list, &prev->n, &slots[idx].n);
}
if (slots[idx].idx >= slot_index) {
slot_index = slots[idx].idx + 1;
}
}
return 0;
}
if (!strncmp(prop, PROP_FWID, len)) {
if (read_cb(cb_arg, &slots[idx].slot.fwid,
sizeof(slots[idx].slot.fwid)) < 0) {
slots[idx].idx = 0;
sys_slist_find_and_remove(&list, &slots[idx].n);
return 0;
}
slots[idx].slot.fwid_len = len_rd;
return 0;
}
if (!strncmp(prop, PROP_METADATA, len)) {
if (read_cb(cb_arg, &slots[idx].slot.metadata,
sizeof(slots[idx].slot.metadata)) < 0) {
slots[idx].idx = 0;
sys_slist_find_and_remove(&list, &slots[idx].n);
return 0;
}
slots[idx].slot.metadata_len = len_rd;
return 0;
}
return 0;
}
SETTINGS_STATIC_HANDLER_DEFINE(bt_mesh_dfu_slots, DFU_SLOT_SETTINGS_PATH, NULL,
slot_data_load, NULL, NULL);