/* * Copyright 2024 Google LLC * * 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 "debug_db.h" #include "drivers/flash.h" #include "flash_region/flash_region.h" #include "system/logging.h" #include "system/passert.h" #include "util/attributes.h" #include "util/math.h" #include "system/version.h" #include //! @file debug_db.c //! //! The flash space is divided into multiple files, and those files are further divided into multiple chunks. Every time //! the system boots up a different file is used. This leaves the file from the previous boot intact in case we previously //! crashed. //! //! Files are referred to in multiple ways. The "file generation" is how recent the file is. 0 is the generation of the current //! boot, 1 is the generation of the previous boot, and so on. The "file index" is which physical slot the file is in. File index //! 0 has the lowest address in flash, where DEBUG_DB_NUM_FILES-1 has the highest. The "file id" is an id that is used to identify //! which generation the file is in. See debug_db_determine_current_index for the logic that is used to convert file ids into //! generations. //! //! The layout for each file looks like the following. //! //! Header //! + Metrics //! v v Logs //! +--+--------+-------------------------------------+ //! | | | | //! | | | | //! +--+--------+-------------------------------------+ //! #define FILE_SIZE_BYTES ((FLASH_REGION_DEBUG_DB_END - FLASH_REGION_DEBUG_DB_BEGIN) / DEBUG_DB_NUM_FILES) #define FILE_ID_BIT_WIDTH 4 #define VERSION_ID_BIT_WIDTH 2 #define CURRENT_VERSION_ID 1 typedef struct PACKED { uint8_t magic:2; // 2) { *current_file_id = get_next_file_id(file_id[i - 1]); *current_file_index = i; return; } } } // Everything was increasing which means everything was in order from oldest to newest // and we need to wrap around. *current_file_index = 0; *current_file_id = get_next_file_id(file_id[DEBUG_DB_NUM_FILES - 1]); } void debug_db_init(void) { // Scan the flash to find out what the two file ids are uint8_t file_id[DEBUG_DB_NUM_FILES] = { INVALID_FILE_ID, INVALID_FILE_ID }; for (int i = 0; i < DEBUG_DB_NUM_FILES; ++i) { FileHeaderBasic file_header; flash_read_bytes((uint8_t*) &file_header, get_file_address(i), sizeof(file_header)); if (file_header.magic == VALID_FILE_HEADER_MAGIC && file_header.version_id == CURRENT_VERSION_ID) { file_id[i] = file_header.file_id; } } debug_db_determine_current_index(file_id, &s_current_file_index, &s_current_file_id); PBL_LOG(LOG_LEVEL_DEBUG, "Found files {%u, %u, %u, %u}, using file %u with new id %u", file_id[0], file_id[1], file_id[2], file_id[3], s_current_file_index, s_current_file_id); debug_db_reformat_header_section(); } bool debug_db_is_generation_valid(int file_generation) { PBL_ASSERTN(file_generation >= 0 && file_generation < DEBUG_DB_NUM_FILES); FileHeaderBasic file_header; flash_read_bytes((uint8_t*) &file_header, get_file_address(generation_to_index(file_generation)), sizeof(file_header)); if (file_header.magic != VALID_FILE_HEADER_MAGIC) { return false; } if (file_header.version_id != CURRENT_VERSION_ID) { return false; } if (file_header.file_id != (s_current_file_id - file_generation)) { PBL_LOG(LOG_LEVEL_DEBUG, "ID: %"PRIu8" Expected: %u", file_header.file_id, (s_current_file_id - file_generation)); return false; } return true; } uint32_t debug_db_get_stats_base_address(int file_generation) { PBL_ASSERTN(file_generation >= 0 && file_generation < DEBUG_DB_NUM_FILES); return get_file_address(generation_to_index(file_generation)) + sizeof(FileHeader); } uint32_t debug_db_get_logs_base_address(int file_generation) { PBL_ASSERTN(file_generation >= 0 && file_generation < DEBUG_DB_NUM_FILES); return get_file_address(generation_to_index(file_generation)) + SECTION_HEADER_SIZE_BYTES; } void debug_db_reformat_header_section(void) { flash_erase_subsector_blocking(get_current_file_address()); FirmwareMetadata md; bool result = version_copy_running_fw_metadata(&md); PBL_ASSERTN(result); FileHeader file_header = { .basic = { .magic = VALID_FILE_HEADER_MAGIC, .file_id = s_current_file_id, .version_id = CURRENT_VERSION_ID }, .details = { .version_tag = "", .is_recovery = md.is_recovery_firmware ? 1 : 0 } }; strncpy(file_header.details.version_tag, md.version_tag, sizeof(md.version_tag)); flash_write_bytes((const uint8_t*) &file_header, get_current_file_address(), sizeof(file_header)); } uint32_t debug_db_get_stat_section_size(void) { return SECTION_HEADER_SIZE_BYTES - sizeof(FileHeader); }