sys/reboot/src/log_reboot.c (347 lines of code) (raw):

/* * 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 }