blob: 1d52ce6c7341e55224a6809d329630116f187800 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 <string.h>
#include <assert.h>
#include "os/mynewt.h"
#include "modlog/modlog.h"
#include "bootutil/image.h"
#include "bootutil/bootutil.h"
#include "img_mgmt/img_mgmt.h"
#include "config/config.h"
#include "config/config_file.h"
#include "reboot/log_reboot.h"
#include "bsp/bsp.h"
#include "flash_map/flash_map.h"
#include "tinycbor/cbor.h"
#include "tinycbor/cbor_buf_writer.h"
uint16_t reboot_cnt;
static int8_t log_reboot_written;
static char *reboot_conf_get(int argc, char **argv, char *buf, int max_len);
static int reboot_conf_set(int argc, char **argv, char *val);
static int reboot_conf_export(void (*export_func)(char *name, char *val),
enum conf_export_tgt tgt);
struct conf_handler reboot_conf_handler = {
.ch_name = "reboot",
.ch_get = reboot_conf_get,
.ch_set = reboot_conf_set,
.ch_commit = NULL,
.ch_export = reboot_conf_export
};
#if MYNEWT_VAL(REBOOT_LOG_FCB)
static struct fcb_log reboot_log_fcb;
static struct log reboot_log;
#if MYNEWT_VAL(LOG_FCB)
static struct flash_area reboot_sector;
#endif
#if MYNEWT_VAL(LOG_FCB2)
static struct flash_sector_range reboot_sector;
#endif
#endif
#if MYNEWT_VAL(REBOOT_LOG_CONSOLE)
static int
log_reboot_init_console(void)
{
int rc;
rc = modlog_register(LOG_MODULE_REBOOT, log_console_get(), LOG_SYSLEVEL,
NULL);
if (rc != 0) {
return rc;
}
return 0;
}
#endif
#if MYNEWT_VAL(REBOOT_LOG_FCB)
static int
log_reboot_init_fcb(void)
{
const struct flash_area *ptr;
#if MYNEWT_VAL(LOG_FCB)
struct fcb *fcbp;
#elif MYNEWT_VAL(LOG_FCB2)
struct fcb2 *fcbp;
#endif
int rc;
if (flash_area_open(MYNEWT_VAL(REBOOT_LOG_FLASH_AREA), &ptr)) {
return SYS_EUNKNOWN;
}
reboot_log_fcb.fl_entries = MYNEWT_VAL(REBOOT_LOG_ENTRY_COUNT);
fcbp = &reboot_log_fcb.fl_fcb;
#if MYNEWT_VAL(LOG_FCB)
reboot_sector = *ptr;
fcbp->f_magic = 0x7EADBADF;
fcbp->f_version = g_log_info.li_version;
fcbp->f_sector_cnt = 1;
fcbp->f_sectors = &reboot_sector;
rc = fcb_init(fcbp);
if (rc) {
flash_area_erase(ptr, 0, ptr->fa_size);
rc = fcb_init(fcbp);
if (rc) {
return rc;
}
}
#endif
#if MYNEWT_VAL(LOG_FCB2)
fcbp->f_magic = 0x8EADBAE0;
fcbp->f_version = g_log_info.li_version;
fcbp->f_sector_cnt = 1;
fcbp->f_range_cnt = 1;
fcbp->f_ranges = &reboot_sector;
reboot_sector.fsr_flash_area = *ptr;
reboot_sector.fsr_sector_count = 1;
reboot_sector.fsr_sector_size = ptr->fa_size;
reboot_sector.fsr_align = flash_area_align(ptr);
rc = fcb2_init(fcbp);
if (rc) {
flash_area_erase(ptr, 0, ptr->fa_size);
rc = fcb2_init(fcbp);
if (rc) {
return rc;
}
}
#endif
rc = log_register("reboot_log", &reboot_log, &log_fcb_handler,
&reboot_log_fcb, LOG_SYSLEVEL);
if (rc != 0) {
return rc;
}
rc = modlog_register(LOG_MODULE_REBOOT, &reboot_log, LOG_SYSLEVEL,
NULL);
if (rc != 0) {
return rc;
}
return 0;
}
#endif
static int
reboot_cnt_inc(void)
{
char str[12];
int rc;
reboot_cnt++;
rc = conf_save_one("reboot/reboot_cnt",
conf_str_from_value(CONF_INT16, &reboot_cnt,
str, sizeof(str)));
return rc;
}
/**
* Logs reboot with the specified reason
* @param reason for reboot
* @return 0 on success; non-zero on failure
*/
static int
log_reboot_write(const struct log_reboot_info *info)
{
struct image_version ver;
uint8_t hash[32];
char buf[MYNEWT_VAL(REBOOT_LOG_BUF_SIZE)];
uint8_t cbor_enc_buf[MYNEWT_VAL(REBOOT_LOG_BUF_SIZE)];
int off;
int rc;
int i;
struct cbor_buf_writer writer;
struct CborEncoder enc;
struct CborEncoder map;
size_t cbor_buf_len;
uint32_t flags;
uint8_t state_flags;
#if MYNEWT_VAL(REBOOT_LOG_FCB)
{
const struct flash_area *ptr;
if (flash_area_open(MYNEWT_VAL(REBOOT_LOG_FLASH_AREA), &ptr)) {
return 0;
}
}
#endif
rc = img_mgmt_read_info(boot_current_slot, &ver, hash, &flags);
if (rc != 0) {
return rc;
}
memset(cbor_enc_buf, 0, sizeof cbor_enc_buf);
cbor_buf_writer_init(&writer, cbor_enc_buf, sizeof cbor_enc_buf);
cbor_encoder_init(&enc, &writer.enc, 0);
rc = cbor_encoder_create_map(&enc, &map, CborIndefiniteLength);
if (rc != 0) {
return rc;
}
cbor_encode_text_stringz(&map, "rsn");
cbor_encode_text_stringz(&map,log_reboot_reason_str(info->reason));
cbor_encode_text_stringz(&map, "cnt");
cbor_encode_int(&map, reboot_cnt);
cbor_encode_text_stringz(&map, "img");
snprintf(buf, sizeof buf, "%u.%u.%u.%u",
ver.iv_major, ver.iv_minor, ver.iv_revision,
(unsigned int)ver.iv_build_num);
cbor_encode_text_stringz(&map, buf);
cbor_encode_text_stringz(&map, "hash");
off = 0;
for (i = 0; i < sizeof hash; i++) {
off += snprintf(buf + off, sizeof buf - off, "%02x",
(unsigned int)hash[i]);
}
cbor_encode_text_stringz(&map, buf);
if (info->file != NULL) {
cbor_encode_text_stringz(&map, "die");
off = 0;
/* If die filename is longer than 1/3 of total allocated
* buffer, then trim the filename from left. */
if (strlen(info->file) > ((sizeof buf) / 3)) {
off = strlen(info->file) - ((sizeof buf) / 3);
}
snprintf(buf, sizeof buf, "%s:%d",
&info->file[off], info->line);
cbor_encode_text_stringz(&map, buf);
}
if (info->pc != 0) {
cbor_encode_text_stringz(&map, "pc");
cbor_encode_int(&map, info->pc);
}
state_flags = img_mgmt_state_flags(boot_current_slot);
cbor_encode_text_stringz(&map, "flags");
off = 0;
buf[0] = '\0';
if (state_flags & IMG_MGMT_STATE_F_ACTIVE) {
off += snprintf(buf + off, sizeof buf - off, "%s ", "active");
}
if (!(flags & IMAGE_F_NON_BOOTABLE)) {
off += snprintf(buf + off, sizeof buf - off, "%s ", "bootable");
}
if (state_flags & IMG_MGMT_STATE_F_CONFIRMED) {
off += snprintf(buf + off, sizeof buf - off, "%s ", "confirmed");
}
if (state_flags & IMG_MGMT_STATE_F_PENDING) {
off += snprintf(buf + off, sizeof buf - off, "%s ", "pending");
}
if (off > 1) {
buf[off - 1] = '\0';
}
cbor_encode_text_stringz(&map, buf);
/* Find length of the CBOR encoded log entry. */
cbor_buf_len = cbor_buf_writer_buffer_size(&writer, cbor_enc_buf) + 1;
rc = cbor_encoder_close_container(&enc, &map);
if (rc != 0) {
return SYS_ENOMEM;
}
/* Log a CBOR encoded reboot record. */
modlog_append(LOG_MODULE_REBOOT, LOG_LEVEL_CRITICAL, LOG_ETYPE_CBOR,
cbor_enc_buf, cbor_buf_len);
return 0;
}
int
log_reboot(const struct log_reboot_info *info)
{
int rc;
/* Don't log a second reboot entry. */
if (log_reboot_written) {
return 0;
}
rc = log_reboot_write(info);
if (rc != 0) {
return rc;
}
if (info->reason != HAL_RESET_REQUESTED &&
info->reason != HAL_RESET_DFU) {
/* Record that we have written a reboot entry for the current boot.
* Upon rebooting, we won't write a second entry.
*/
log_reboot_written = 1;
conf_save_one("reboot/written", "1");
}
return 0;
}
/**
* Increments the reboot counter and writes an entry to the reboot log, if
* necessary. This function should be called from main() after config
* settings have been loaded via conf_load().
*
* @param reason The cause of the reboot.
*/
void
reboot_start(enum hal_reset_reason reason)
{
struct log_reboot_info info;
/* If an entry wasn't written before the previous reboot, write one now. */
if (!log_reboot_written) {
reboot_cnt_inc();
info = (struct log_reboot_info) {
.reason = reason,
.file = NULL,
.line = 0,
.pc = 0,
};
log_reboot_write(&info);
}
/* Record that we haven't written a reboot entry for the current boot. */
log_reboot_written = 0;
conf_save_one("reboot/written", "0");
}
static char *
reboot_conf_get(int argc, char **argv, char *buf, int max_len)
{
if (argc == 1) {
if (!strcmp(argv[0], "reboot_cnt")) {
return conf_str_from_value(CONF_INT16, &reboot_cnt, buf, max_len);
} else if (!strcmp(argv[0], "written")) {
return conf_str_from_value(CONF_BOOL, &log_reboot_written,
buf, max_len);
}
}
return NULL;
}
static int
reboot_conf_set(int argc, char **argv, char *val)
{
if (argc == 1) {
if (!strcmp(argv[0], "reboot_cnt")) {
return CONF_VALUE_SET(val, CONF_INT16, reboot_cnt);
} else if (!strcmp(argv[0], "written")) {
return CONF_VALUE_SET(val, CONF_INT16, log_reboot_written);
}
}
return OS_ENOENT;
}
static int
reboot_conf_export(void (*func)(char *name, char *val),
enum conf_export_tgt tgt)
{
char str[12];
if (tgt == CONF_EXPORT_SHOW) {
func("reboot/reboot_cnt",
conf_str_from_value(CONF_INT16, &reboot_cnt, str, sizeof str));
func("reboot/written",
conf_str_from_value(CONF_BOOL, &log_reboot_written, str,
sizeof str));
}
return 0;
}
const char *
log_reboot_reason_str(enum hal_reset_reason reason)
{
static char str_reason[MYNEWT_VAL(REBOOT_LOG_REBOOT_REASON_SIZE)];
if (reason >= HAL_RESET_OTHER) {
snprintf(str_reason,MYNEWT_VAL(REBOOT_LOG_REBOOT_REASON_SIZE),"OTHER: 0x%X",reason - HAL_RESET_OTHER);
return str_reason;
}
switch (reason) {
case HAL_RESET_POR:
return "HARD";
break;
case HAL_RESET_PIN:
return "RESET_PIN";
break;
case HAL_RESET_WATCHDOG:
return "WDOG";
break;
case HAL_RESET_SOFT:
return "SOFT";
break;
case HAL_RESET_BROWNOUT:
return "BROWNOUT";
break;
case HAL_RESET_REQUESTED:
return "REQUESTED";
break;
case HAL_RESET_SYS_OFF_INT:
return "SYSTEM_OFF_INT";
break;
case HAL_RESET_DFU:
return "DFU";
break;
default:
snprintf(str_reason,MYNEWT_VAL(REBOOT_LOG_REBOOT_REASON_SIZE),"UNKNOWN %d",reason);
return str_reason;
break;
}
}
void
log_reboot_pkg_init(void)
{
int rc;
/* Ensure this function only gets called by sysinit. */
SYSINIT_ASSERT_ACTIVE();
rc = conf_register(&reboot_conf_handler);
SYSINIT_PANIC_ASSERT(rc == 0);
#if MYNEWT_VAL(REBOOT_LOG_FCB)
rc = log_reboot_init_fcb();
SYSINIT_PANIC_ASSERT(rc == 0);
#endif
#if MYNEWT_VAL(REBOOT_LOG_CONSOLE)
rc = log_reboot_init_console();
SYSINIT_PANIC_ASSERT(rc == 0);
#endif
}