Rebase private OTA libs on 2.7

This commit is contained in:
Pim van Pelt
2018-10-28 12:12:24 +01:00
parent 497c62b382
commit 9ee393bc8a
29 changed files with 3836 additions and 521 deletions

View File

@ -0,0 +1,505 @@
/*
* Copyright (c) 2014-2018 Cesanta Software Limited
* All rights reserved
*
* Licensed under the Apache License, Version 2.0 (the ""License"");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an ""AS IS"" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "cc3200_updater.h"
#include <inttypes.h>
#include <strings.h>
#include "common/cs_dbg.h"
#include "common/platform.h"
#include "frozen.h"
#include "mongoose.h"
#include "mgos_hal.h"
#include "mgos_sys_config.h"
#include "mgos_updater_hal.h"
#include "mgos_updater_util.h"
#include "mgos_utils.h"
#include "cc32xx_hash.h"
#include "cc3200_vfs_dev_slfs_container.h"
#include "cc3200_vfs_dev_slfs_container_meta.h"
#include "fw/platforms/cc3200/boot/lib/boot.h"
static int s_boot_cfg_idx;
static struct boot_cfg s_boot_cfg;
struct mgos_upd_hal_ctx {
struct json_token *parts;
int cur_boot_cfg_idx;
int new_boot_cfg_idx;
char app_image_file[MAX_APP_IMAGE_FILE_LEN];
uint32_t app_load_addr;
char fs_container_file[MAX_FS_CONTAINER_FNAME_LEN + 3];
uint32_t fs_size, fs_block_size, fs_page_size, fs_erase_size;
struct json_token cur_part;
const _u8 *cur_fn;
_i32 cur_fh;
const char *status_msg;
};
struct mgos_upd_hal_ctx *mgos_upd_hal_ctx_create(void) {
struct mgos_upd_hal_ctx *ctx =
(struct mgos_upd_hal_ctx *) calloc(1, sizeof(*ctx));
ctx->cur_fh = -1;
return ctx;
}
const char *mgos_upd_get_status_msg(struct mgos_upd_hal_ctx *ctx) {
return ctx->status_msg;
}
int mgos_upd_begin(struct mgos_upd_hal_ctx *ctx, struct json_token *parts) {
ctx->parts = parts;
/* We want to make sure device uses boto loader. */
ctx->cur_boot_cfg_idx = get_active_boot_cfg_idx();
if (ctx->cur_boot_cfg_idx < 0) {
ctx->status_msg = "Could not read current boot cfg";
return -1;
}
ctx->new_boot_cfg_idx = get_inactive_boot_cfg_idx();
return 1;
}
typedef int (*read_file_cb_t)(_u8 *data, int len, void *arg);
static int read_file(const char *fn, int offset, int len, read_file_cb_t cb,
void *arg) {
_i32 fh;
int r = sl_FsOpen((const _u8 *) fn, FS_MODE_OPEN_READ, NULL, &fh);
if (r < 0) return r;
while (len > 0) {
_u8 buf[512];
int to_read = MIN(len, sizeof(buf));
r = sl_FsRead(fh, offset, buf, to_read);
if (r != to_read) break;
if (cb(buf, to_read, arg) != to_read) break;
offset += to_read;
len -= to_read;
}
sl_FsClose(fh, NULL, NULL, 0);
return (len == 0 ? 0 : -1);
}
static int sha1_update_cb(_u8 *data, int len, void *arg) {
cc32xx_hash_update((struct cc32xx_hash_ctx *) arg, data, len);
return len;
}
void bin2hex(const uint8_t *src, int src_len, char *dst);
static bool verify_checksum(const char *fn, int fs,
const struct json_token *expected) {
_u8 digest[20];
char digest_str[41];
if (expected->len != 40) {
return false;
}
struct cc32xx_hash_ctx ctx;
cc32xx_hash_init(&ctx);
cc32xx_hash_start(&ctx, CC32XX_HASH_ALGO_SHA1);
if (read_file(fn, 0, fs, sha1_update_cb, &ctx) < 0) {
return false;
}
cc32xx_hash_finish(&ctx, digest);
bin2hex(digest, 20, digest_str);
LOG(LL_INFO,
("%s: have %.*s, want %.*s", fn, 40, digest_str, 40, expected->ptr));
return (strncasecmp(expected->ptr, digest_str, 40) == 0);
}
/* Create file name by appending ".$idx" to prefix. */
static void create_fname(struct mg_str pfx, int idx, char *fn, int len) {
int l = MIN(len - 3, pfx.len);
memcpy(fn, pfx.p, l);
fn[l++] = '.';
fn[l++] = ('0' + idx);
fn[l] = '\0';
}
static int prepare_to_write(struct mgos_upd_hal_ctx *ctx,
const struct mgos_upd_file_info *fi,
const char *fname, uint32_t falloc,
struct json_token *part) {
struct json_token expected_sha1 = JSON_INVALID_TOKEN;
json_scanf(part->ptr, part->len, "{cs_sha1: %T}", &expected_sha1);
if (verify_checksum(fname, fi->size, &expected_sha1)) {
LOG(LL_INFO,
("Digest matched for %s %u (%.*s)", fname, (unsigned int) fi->size,
(int) expected_sha1.len, expected_sha1.ptr));
return 0;
}
LOG(LL_INFO, ("Storing %s %u -> %s %u (%.*s)", fi->name,
(unsigned int) fi->size, fname, (unsigned int) falloc,
(int) expected_sha1.len, expected_sha1.ptr));
ctx->cur_fn = (const _u8 *) fname;
sl_FsDel(ctx->cur_fn, 0);
_i32 r = sl_FsOpen(ctx->cur_fn, FS_MODE_OPEN_CREATE(falloc, 0), NULL,
&ctx->cur_fh);
if (r < 0) {
ctx->status_msg = "Failed to create file";
return r;
}
return 1;
}
struct find_part_info {
const char *src;
struct mg_str *key;
struct json_token *value;
char buf[50];
};
static void find_part(void *data, const char *name, size_t name_len,
const char *path, const struct json_token *tok) {
struct find_part_info *info = (struct find_part_info *) data;
size_t path_len = strlen(path), src_len = strlen(info->src);
(void) name;
(void) name_len;
if (tok->ptr == NULL) {
/*
* We're not interested here in the events for which we have no value;
* namely, JSON_TYPE_OBJECT_START and JSON_TYPE_ARRAY_START
*/
return;
}
/* For matched 'src' attribute, remember parent object path. */
if (src_len == tok->len && strncmp(info->src, tok->ptr, tok->len) == 0) {
const char *p = path + path_len;
while (--p > path + 1) {
if (*p == '.') break;
}
info->key->len = snprintf(info->buf, sizeof(info->buf), "%.*s",
(p - path) - 1, path + 1);
info->key->p = info->buf;
}
/*
* And store parent's object token. These conditionals are triggered
* in separate callback invocations.
*/
if (info->value->len == 0 && info->key->len > 0 &&
info->key->len + 1 == path_len &&
strncmp(path + 1, info->key->p, info->key->len) == 0) {
*info->value = *tok;
}
}
static int tcmp(const struct json_token *tok, const char *str) {
struct mg_str s = {.p = tok->ptr, .len = tok->len};
return mg_vcmp(&s, str);
}
enum mgos_upd_file_action mgos_upd_file_begin(
struct mgos_upd_hal_ctx *ctx, const struct mgos_upd_file_info *fi) {
struct mg_str part_name = MG_MK_STR("");
enum mgos_upd_file_action ret = MGOS_UPDATER_SKIP_FILE;
struct find_part_info find_part_info = {fi->name, &part_name, &ctx->cur_part};
ctx->cur_part.len = part_name.len = 0;
json_walk(ctx->parts->ptr, ctx->parts->len, find_part, &find_part_info);
if (ctx->cur_part.len == 0) return ret;
/* Drop any indexes from part name, we'll add our own. */
while (1) {
char c = part_name.p[part_name.len - 1];
if (c != '.' && !(c >= '0' && c <= '9')) break;
part_name.len--;
}
struct json_token type = JSON_INVALID_TOKEN;
const char *fname = NULL;
uint32_t falloc = 0;
json_scanf(ctx->cur_part.ptr, ctx->cur_part.len,
"{load_addr:%u, falloc:%u, type: %T}", &ctx->app_load_addr,
&falloc, &type);
if (falloc == 0) falloc = fi->size;
if (tcmp(&type, "app") == 0) {
struct boot_cfg cur_cfg;
int r = read_boot_cfg(ctx->cur_boot_cfg_idx, &cur_cfg);
if (r < 0) {
ctx->status_msg = "Could not read current boot cfg";
return MGOS_UPDATER_ABORT;
}
#if CC3200_SAFE_CODE_UPDATE
/*
* When safe code update is enabled, we write code to a new file.
* Otherwise we write to the same slot we're using currently, which is
* unsafe, makes reverting code update not possible, but saves space.
*/
create_fname(
mg_mk_str_n(cur_cfg.app_image_file, strlen(cur_cfg.app_image_file) - 2),
ctx->new_boot_cfg_idx, ctx->app_image_file,
sizeof(ctx->app_image_file));
#else
{
strncpy(ctx->app_image_file, cur_cfg.app_image_file,
sizeof(ctx->app_image_file));
}
#endif
if (ctx->app_load_addr >= 0x20000000) {
fname = ctx->app_image_file;
} else {
ctx->status_msg = "Bad/missing app load_addr";
ret = MGOS_UPDATER_ABORT;
}
} else if (tcmp(&type, "fs") == 0) {
json_scanf(
ctx->cur_part.ptr, ctx->cur_part.len,
"{fs_size: %u, fs_block_size: %u, fs_page_size: %u, fs_erase_size: %u}",
&ctx->fs_size, &ctx->fs_block_size, &ctx->fs_page_size,
&ctx->fs_erase_size);
if (ctx->fs_size > 0 && ctx->fs_block_size > 0 && ctx->fs_page_size > 0 &&
ctx->fs_erase_size > 0) {
char fs_container_prefix[MAX_FS_CONTAINER_PREFIX_LEN];
create_fname(part_name, ctx->new_boot_cfg_idx, fs_container_prefix,
sizeof(fs_container_prefix));
/* Delete container 1 (if any) so that 0 is the only one. */
cc3200_vfs_dev_slfs_container_delete_container(fs_container_prefix, 1);
cc3200_vfs_dev_slfs_container_fname(fs_container_prefix, 0,
(_u8 *) ctx->fs_container_file);
fname = ctx->fs_container_file;
if (fi->size > ctx->fs_size) {
/* Assume meta has already been added. */
falloc = fi->size;
} else {
falloc = FS_CONTAINER_SIZE(fi->size);
}
} else {
ctx->status_msg = "Missing FS parameters";
ret = MGOS_UPDATER_ABORT;
}
}
if (fname != NULL) {
int r = prepare_to_write(ctx, fi, fname, falloc, &ctx->cur_part);
if (r < 0) {
LOG(LL_ERROR, ("err = %d", r));
ret = MGOS_UPDATER_ABORT;
} else {
ret = (r > 0 ? MGOS_UPDATER_PROCESS_FILE : MGOS_UPDATER_SKIP_FILE);
}
}
if (ret == MGOS_UPDATER_SKIP_FILE) {
DBG(("Skipping %s %.*s", fi->name, (int) part_name.len, part_name.p));
}
return ret;
}
int mgos_upd_file_data(struct mgos_upd_hal_ctx *ctx,
const struct mgos_upd_file_info *fi,
struct mg_str data) {
_i32 r = sl_FsWrite(ctx->cur_fh, fi->processed, (_u8 *) data.p, data.len);
if (r != data.len) {
ctx->status_msg = "Write failed";
r = -1;
}
return r;
}
int mgos_upd_file_end(struct mgos_upd_hal_ctx *ctx,
const struct mgos_upd_file_info *fi, struct mg_str tail) {
int r = tail.len;
assert(tail.len == 0);
if (ctx->cur_fn == (_u8 *) ctx->fs_container_file) {
if (!cc3200_vfs_dev_slfs_container_write_meta(
ctx->cur_fh, FS_INITIAL_SEQ, ctx->fs_size, ctx->fs_block_size,
ctx->fs_page_size, ctx->fs_erase_size)) {
ctx->status_msg = "Failed to write fs meta";
r = -1;
}
}
if (sl_FsClose(ctx->cur_fh, NULL, NULL, 0) != 0) {
ctx->status_msg = "Close failed";
r = -1;
} else {
struct json_token sha1 = JSON_INVALID_TOKEN;
json_scanf(ctx->cur_part.ptr, ctx->cur_part.len, "{cs_sha1: %T}", &sha1);
if (!verify_checksum((const char *) ctx->cur_fn, fi->size, &sha1)) {
ctx->status_msg = "Checksum mismatch";
r = -1;
}
}
ctx->cur_fh = -1;
ctx->cur_fn = NULL;
return r;
}
int mgos_upd_finalize(struct mgos_upd_hal_ctx *ctx) {
struct boot_cfg cur_cfg, new_cfg;
int r = read_boot_cfg(ctx->cur_boot_cfg_idx, &cur_cfg);
if (r < 0) {
ctx->status_msg = "Could not read current boot cfg";
return r;
}
LOG(LL_INFO,
("Boot cfg %d: 0x%llx, 0x%x, %s @ 0x%08x, %s", ctx->cur_boot_cfg_idx,
cur_cfg.seq, (unsigned int) cur_cfg.flags, cur_cfg.app_image_file,
(unsigned int) cur_cfg.app_load_addr, cur_cfg.fs_container_prefix));
memset(&new_cfg, 0, sizeof(new_cfg));
new_cfg.seq = cur_cfg.seq - 1;
new_cfg.flags |= BOOT_F_FIRST_BOOT;
if (ctx->app_image_file[0] != '\0') {
strncpy(new_cfg.app_image_file, ctx->app_image_file,
sizeof(new_cfg.app_image_file));
new_cfg.app_load_addr = ctx->app_load_addr;
} else {
strcpy(new_cfg.app_image_file, cur_cfg.app_image_file);
new_cfg.app_load_addr = cur_cfg.app_load_addr;
}
if (ctx->fs_container_file[0] != '\0') {
int n = strlen(ctx->fs_container_file);
do {
n--;
} while (ctx->fs_container_file[n] != '.');
strncpy(new_cfg.fs_container_prefix, ctx->fs_container_file, n);
new_cfg.flags |= BOOT_F_MERGE_SPIFFS;
} else {
strcpy(new_cfg.fs_container_prefix, cur_cfg.fs_container_prefix);
}
LOG(LL_INFO,
("Boot cfg %d: 0x%llx, 0x%x, %s @ 0x%08x, %s", ctx->new_boot_cfg_idx,
new_cfg.seq, (unsigned int) new_cfg.flags, new_cfg.app_image_file,
(unsigned int) new_cfg.app_load_addr, new_cfg.fs_container_prefix));
r = write_boot_cfg(&new_cfg, ctx->new_boot_cfg_idx);
if (r < 0) {
ctx->status_msg = "Could not write new boot cfg";
return r;
}
return 1;
}
void mgos_upd_hal_ctx_free(struct mgos_upd_hal_ctx *ctx) {
if (ctx == NULL) return;
if (ctx->cur_fh >= 0) sl_FsClose(ctx->cur_fh, NULL, NULL, 0);
if (ctx->cur_fn != NULL) sl_FsDel(ctx->cur_fn, 0);
memset(ctx, 0, sizeof(*ctx));
free(ctx);
}
int mgos_upd_create_snapshot() {
/* TODO(rojer): Implement. */
return -1;
}
bool cc3200_upd_init(void) {
s_boot_cfg_idx = get_active_boot_cfg_idx();
if (s_boot_cfg_idx < 0 || read_boot_cfg(s_boot_cfg_idx, &s_boot_cfg) < 0) {
return false;
}
LOG(LL_INFO,
("Boot cfg %d: 0x%llx, 0x%u, %s @ 0x%08x, %s", s_boot_cfg_idx,
s_boot_cfg.seq, (unsigned int) s_boot_cfg.flags,
s_boot_cfg.app_image_file, (unsigned int) s_boot_cfg.app_load_addr,
s_boot_cfg.fs_container_prefix));
if (mgos_upd_is_first_boot()) {
/* Tombstone the current config. If anything goes wrong between now and
* commit, next boot will use the old one. */
uint64_t saved_seq = s_boot_cfg.seq;
s_boot_cfg.seq = BOOT_CFG_TOMBSTONE_SEQ;
write_boot_cfg(&s_boot_cfg, s_boot_cfg_idx);
s_boot_cfg.seq = saved_seq;
}
/*
* We aim to maintain at most 3 FS containers at all times.
* Delete inactive FS container in the inactive boot configuration.
*/
struct boot_cfg cfg;
int inactive_idx = (s_boot_cfg_idx == 0 ? 1 : 0);
if (read_boot_cfg(inactive_idx, &cfg) >= 0) {
cc3200_vfs_dev_slfs_container_delete_inactive_container(
cfg.fs_container_prefix);
}
return true;
}
const char *cc3200_upd_get_fs_container_prefix(void) {
return s_boot_cfg.fs_container_prefix;
}
bool mgos_upd_boot_get_state(struct mgos_upd_boot_state *bs) {
struct boot_cfg *cfg = &s_boot_cfg;
memset(bs, 0, sizeof(*bs));
const char *p = strrchr(cfg->app_image_file, '.');
bs->active_slot = (*(p + 1) == '0' ? 0 : 1);
bs->revert_slot = (bs->active_slot == 0 ? 1 : 0);
bs->is_committed = !(cfg->flags & BOOT_F_FIRST_BOOT);
return true;
}
bool mgos_upd_boot_set_state(const struct mgos_upd_boot_state *bs) {
/* TODO(rojer): Implement. */
(void) bs;
return false;
}
void mgos_upd_boot_revert(void) {
int boot_cfg_idx = s_boot_cfg_idx;
struct boot_cfg *cfg = &s_boot_cfg;
if (!cfg->flags & BOOT_F_FIRST_BOOT) return;
LOG(LL_ERROR, ("Config %d is bad, reverting", boot_cfg_idx));
/* Tombstone the current config. */
cfg->seq = BOOT_CFG_TOMBSTONE_SEQ;
write_boot_cfg(cfg, boot_cfg_idx);
}
bool mgos_upd_is_first_boot(void) {
return (s_boot_cfg.flags & BOOT_F_FIRST_BOOT) != 0;
}
void mgos_upd_boot_commit(void) {
int boot_cfg_idx = s_boot_cfg_idx;
struct boot_cfg *cfg = &s_boot_cfg;
if (!cfg->flags & BOOT_F_FIRST_BOOT) return;
cfg->flags &= ~(BOOT_F_FIRST_BOOT);
int r = write_boot_cfg(cfg, boot_cfg_idx);
if (r < 0) mgos_upd_boot_revert();
LOG(LL_INFO, ("Committed cfg %d, seq 0x%llx", boot_cfg_idx, cfg->seq));
}
int mgos_upd_apply_update(void) {
int boot_cfg_idx = s_boot_cfg_idx;
struct boot_cfg *cfg = &s_boot_cfg;
if (cfg->flags & BOOT_F_MERGE_SPIFFS) {
int old_boot_cfg_idx = (boot_cfg_idx == 0 ? 1 : 0);
struct boot_cfg old_boot_cfg;
int r = read_boot_cfg(old_boot_cfg_idx, &old_boot_cfg);
if (r < 0) return r;
if (!cc3200_fs_container_mount("/old", old_boot_cfg.fs_container_prefix)) {
return -123;
}
if (mgos_upd_merge_fs("/old", "/")) {
r = 0;
cfg->flags &= ~(BOOT_F_MERGE_SPIFFS);
} else {
r = -124;
}
mgos_vfs_umount("/old");
}
return 0;
}