mirror of
https://github.com/google/pebble.git
synced 2025-07-15 10:36:43 -04:00
Import of the watch repository from Pebble
This commit is contained in:
commit
3b92768480
10334 changed files with 2564465 additions and 0 deletions
229
src/fw/debug/legacy/debug_db.c
Normal file
229
src/fw/debug/legacy/debug_db.c
Normal file
|
@ -0,0 +1,229 @@
|
|||
/*
|
||||
* 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 <inttypes.h>
|
||||
|
||||
//! @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; //<! Set to 0x2 if valid.
|
||||
|
||||
//! The file id of this file. We always replace the oldest of the two files if they're both
|
||||
//! valid. Serial distance is used to determine if the id overflowed or not.
|
||||
uint8_t file_id:FILE_ID_BIT_WIDTH;
|
||||
|
||||
//! Just in case we have to change this struct in the future.
|
||||
uint8_t version_id:VERSION_ID_BIT_WIDTH;
|
||||
} FileHeaderBasic;
|
||||
|
||||
typedef struct PACKED {
|
||||
char version_tag[FW_METADATA_VERSION_TAG_BYTES];
|
||||
uint8_t is_recovery;
|
||||
} FileHeaderDetails;
|
||||
|
||||
typedef struct PACKED {
|
||||
FileHeaderBasic basic;
|
||||
FileHeaderDetails details;
|
||||
} FileHeader;
|
||||
|
||||
//! This value is chosen because older style (pre In-N-Out) filesystems set the first bit to zero to indicate that it's a
|
||||
//! valid chunk. We should consider those invalid (different format) so we want to see a 1 there if it's actually a post-In-N-Out
|
||||
//! file. Then, we set the second bit to 0 to differentiate it from unformatted SPI flash, as newly erased SPI flash will have the
|
||||
//! value 0x03 (both bits set).
|
||||
static const uint8_t VALID_FILE_HEADER_MAGIC = 0x02;
|
||||
|
||||
//! Which file we're writing to this boot. [0 - DEBUG_DB_NUM_FILES)
|
||||
static int s_current_file_index;
|
||||
//! The id we're using for the current file.
|
||||
static uint8_t s_current_file_id;
|
||||
|
||||
static int generation_to_index(int file_generation) {
|
||||
int index = s_current_file_index - file_generation;
|
||||
if (index < 0) {
|
||||
index += DEBUG_DB_NUM_FILES;
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
static uint32_t get_file_address(int file_index) {
|
||||
return FLASH_REGION_DEBUG_DB_BEGIN + (file_index * FILE_SIZE_BYTES);
|
||||
}
|
||||
|
||||
static uint32_t get_current_file_address(void) {
|
||||
return get_file_address(generation_to_index(0));
|
||||
}
|
||||
|
||||
//! Get next FILE_ID_BIT_WIDTH bit value
|
||||
static uint8_t get_next_file_id(uint8_t file_id) {
|
||||
return (file_id + 1) % (1 << (FILE_ID_BIT_WIDTH));
|
||||
}
|
||||
|
||||
// Make sure this is out of range for uint8_t:FILE_ID_BIT_WIDTH.
|
||||
static const uint8_t INVALID_FILE_ID = 0xff;
|
||||
|
||||
void debug_db_determine_current_index(uint8_t* file_id, int* current_file_index, uint8_t* current_file_id) {
|
||||
for (int i = 0; i < DEBUG_DB_NUM_FILES; ++i) {
|
||||
// If we find an unused slot, use that one. We fill in slots from left to right,
|
||||
// so the first one we find when searching left to right is the one we should use.
|
||||
if (file_id[i] == INVALID_FILE_ID) {
|
||||
*current_file_index = i;
|
||||
if (i == 0) {
|
||||
*current_file_id = 0;
|
||||
} else {
|
||||
*current_file_id = get_next_file_id(file_id[i - 1]);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (i != 0) {
|
||||
// If we find a reduction in an id, this is the end of the sequence and we've found
|
||||
// the oldest file. For example, if the IDs are (5, 6, 3, 4), when we find three we'll
|
||||
// see that the ids have stopped increasing. We should be using index 2 with an id of 7.
|
||||
int32_t distance = serial_distance(file_id[i - 1], file_id[i], FILE_ID_BIT_WIDTH);
|
||||
if (distance < 0 || distance > 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);
|
||||
}
|
||||
|
40
src/fw/debug/legacy/debug_db.h
Normal file
40
src/fw/debug/legacy/debug_db.h
Normal file
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
// These values need to multiples of subsectors (4k) to make them easy to erase.
|
||||
//! Includes the header + the metrics
|
||||
#define SECTION_HEADER_SIZE_BYTES 4096 // Contains both the file header and the stats
|
||||
#define SECTION_LOGS_SIZE_BYTES (4096 * 7)
|
||||
|
||||
#define DEBUG_DB_NUM_FILES 4
|
||||
|
||||
void debug_db_determine_current_index(uint8_t* file_id, int* current_file_index, uint8_t* current_file_id);
|
||||
|
||||
void debug_db_init(void);
|
||||
|
||||
bool debug_db_is_generation_valid(int file_generation);
|
||||
uint32_t debug_db_get_stats_base_address(int file_generation);
|
||||
uint32_t debug_db_get_logs_base_address(int file_generation);
|
||||
|
||||
void debug_db_reformat_header_section(void);
|
||||
|
||||
uint32_t debug_db_get_stat_section_size(void);
|
||||
|
247
src/fw/debug/legacy/flash_logging.c
Normal file
247
src/fw/debug/legacy/flash_logging.c
Normal file
|
@ -0,0 +1,247 @@
|
|||
/*
|
||||
* 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 "board/board.h"
|
||||
#include "debug/flash_logging.h"
|
||||
#include "drivers/flash.h"
|
||||
#include "drivers/rtc.h"
|
||||
#include "drivers/watchdog.h"
|
||||
#include "flash_region/flash_region.h"
|
||||
#include "kernel/pbl_malloc.h"
|
||||
#include "system/logging.h"
|
||||
#include "system/passert.h"
|
||||
#include "kernel/util/sleep.h"
|
||||
#include "util/attributes.h"
|
||||
#include "util/math.h"
|
||||
#include "util/net.h"
|
||||
|
||||
#include <inttypes.h>
|
||||
|
||||
//! @file flash_logging.c
|
||||
//! Logs messages to SPI flash for later retreival.
|
||||
//!
|
||||
//! The different chunks allow us to implement a rolling log, where if we fill up all the chunks, we can erase the oldest
|
||||
//! chunk to find us some more space. Each chunk gets it's own header at the top of the chunk to indicate the order in
|
||||
//! which the chunks should be reassembled.
|
||||
|
||||
//! Make sure chunks are still an even number of flash subsectors. Our log space is 7 subsectors, so our NUM_CHUNKS
|
||||
//! makes it so each chunk has it's own subsector.
|
||||
#define NUM_CHUNKS 7
|
||||
|
||||
#define CHUNK_SIZE_BYTES (SECTION_LOGS_SIZE_BYTES / NUM_CHUNKS)
|
||||
|
||||
#define CHUNK_ID_BIT_WIDTH 8
|
||||
|
||||
//! None of the values in this struct are allowed to be equal to 0xff. 0xff is used as an invalid
|
||||
//! value (as the spi flash sets bytes to 0xff when they're erased)
|
||||
typedef struct PACKED {
|
||||
bool invalid:1; //!< Whether or not this chunk is formatted
|
||||
bool valid:1; //!< Set to 0 when the chunk is stale.
|
||||
|
||||
//! The ID of the current chunk. Each chunk in the gets an auto-incrementing ID. This
|
||||
//! allows the logging infrastructure to find the head and tail of the circular buffer after a
|
||||
//! reboot. The oldest chunk will also have the lowest ID.
|
||||
uint8_t chunk_id:CHUNK_ID_BIT_WIDTH; // :6
|
||||
} LogChunkHeader;
|
||||
|
||||
typedef struct PACKED {
|
||||
//! The length of the log message after this header, not including this header. If this value
|
||||
//! is 0xff that means no log message follows. If this value is 0x0 this means there are no
|
||||
//! more logs remaining in this chunk.
|
||||
uint8_t log_length;
|
||||
} LogHeader;
|
||||
|
||||
//! Which chunk we're writing to. [0 - NUM_CHUNKS)
|
||||
static int s_current_chunk;
|
||||
//! The id we're using for the current chunk.
|
||||
static int s_current_chunk_id;
|
||||
|
||||
//! The current offset in the chunk in bytes. [0 - CHUNK_SIZE_BYTES)
|
||||
static int s_current_offset;
|
||||
|
||||
static bool s_enabled;
|
||||
|
||||
static uint32_t get_current_address(int chunk, int offset) {
|
||||
return debug_db_get_logs_base_address(0) + (chunk * CHUNK_SIZE_BYTES) + offset;
|
||||
}
|
||||
|
||||
static uint32_t get_generation_address(int generation, int chunk, int offset) {
|
||||
return debug_db_get_logs_base_address(generation) + (chunk * CHUNK_SIZE_BYTES) + offset;
|
||||
}
|
||||
|
||||
//! Get next 8 bit value, avoiding 0xff (all bits set)
|
||||
static uint8_t get_next_chunk_id(uint8_t chunk_id) {
|
||||
return (chunk_id + 1) % (1 << (CHUNK_ID_BIT_WIDTH));
|
||||
}
|
||||
|
||||
static void format_current_chunk(void) {
|
||||
uint32_t addr = get_current_address(s_current_chunk, 0);
|
||||
PBL_ASSERT((addr & (SUBSECTOR_SIZE_BYTES - 1)) == 0,
|
||||
"Sections must be subsector aligned! addr is 0x%" PRIx32, addr);
|
||||
PBL_ASSERT((CHUNK_SIZE_BYTES & (SUBSECTOR_SIZE_BYTES - 1)) == 0,
|
||||
"Sections divide into subsectors evenly, size is 0x%" PRIx16, CHUNK_SIZE_BYTES);
|
||||
|
||||
for (unsigned int i = 0; i < (CHUNK_SIZE_BYTES / SUBSECTOR_SIZE_BYTES); ++i) {
|
||||
flash_erase_subsector_blocking(addr + (i * SUBSECTOR_SIZE_BYTES));
|
||||
}
|
||||
|
||||
LogChunkHeader chunk_header = {
|
||||
.invalid = false,
|
||||
.valid = true,
|
||||
.chunk_id = s_current_chunk_id
|
||||
};
|
||||
flash_write_bytes((const uint8_t*) &chunk_header, get_current_address(s_current_chunk, 0), sizeof(chunk_header));
|
||||
|
||||
s_current_offset = sizeof(LogChunkHeader);
|
||||
}
|
||||
|
||||
static void make_space_for_log(int length) {
|
||||
if (s_current_offset + sizeof(LogHeader) + length + sizeof(LogHeader) < CHUNK_SIZE_BYTES) {
|
||||
// We got space, nothing to do here
|
||||
return;
|
||||
}
|
||||
|
||||
// Need to roll over to the next chunk
|
||||
|
||||
// Seal off the current chunk with a 0 length log message.
|
||||
LogHeader log_header = { .log_length = 0 };
|
||||
flash_write_bytes((const uint8_t*) &log_header, get_current_address(s_current_chunk, s_current_offset), sizeof(log_header));
|
||||
|
||||
// Set up the next chunk.
|
||||
s_current_chunk = (s_current_chunk + 1) % NUM_CHUNKS;
|
||||
s_current_chunk_id = get_next_chunk_id(s_current_chunk_id);
|
||||
format_current_chunk();
|
||||
}
|
||||
|
||||
uint32_t flash_logging_log_start(uint8_t msg_length) {
|
||||
make_space_for_log(msg_length);
|
||||
|
||||
LogHeader log_header = { .log_length = msg_length };
|
||||
flash_write_bytes((const uint8_t*) &log_header, get_current_address(s_current_chunk, s_current_offset), sizeof(log_header));
|
||||
s_current_offset += sizeof(log_header);
|
||||
|
||||
uint32_t addr = get_current_address(s_current_chunk, s_current_offset);
|
||||
s_current_offset += msg_length;
|
||||
return addr;
|
||||
}
|
||||
|
||||
bool flash_logging_write(const uint8_t *data_to_write, uint32_t flash_addr,
|
||||
uint32_t data_length) {
|
||||
flash_write_bytes(data_to_write, flash_addr, data_length);
|
||||
return (true);
|
||||
}
|
||||
|
||||
void flash_logging_init(void) {
|
||||
debug_db_init();
|
||||
s_current_chunk = 0;
|
||||
s_current_chunk_id = 0;
|
||||
|
||||
// Formatting the file we're going to use by erasing the first chunk and writing a new header.
|
||||
format_current_chunk();
|
||||
|
||||
// Mark all the other chunks as stale. This will mark the "valid" member of LogChunkHeader to 0.
|
||||
for (int i = 1; i < NUM_CHUNKS; ++i) {
|
||||
uint8_t zero = 0;
|
||||
flash_write_bytes(&zero, get_current_address(i, 0), sizeof(zero));
|
||||
}
|
||||
|
||||
s_enabled = true;
|
||||
}
|
||||
|
||||
// Dumping commands
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
static bool dump_chunk(int generation, int chunk_index, DumpLineCallback cb) {
|
||||
int offset = sizeof(LogChunkHeader);
|
||||
uint8_t* read_buffer = kernel_malloc(256);
|
||||
bool error = false;
|
||||
|
||||
while (!error) {
|
||||
LogHeader log_header;
|
||||
flash_read_bytes((uint8_t*) &log_header, get_generation_address(generation, chunk_index, offset), sizeof(LogHeader));
|
||||
|
||||
if (log_header.log_length == 0 || log_header.log_length == 0xff) {
|
||||
break;
|
||||
}
|
||||
|
||||
offset += sizeof(LogHeader);
|
||||
|
||||
flash_read_bytes(read_buffer, get_generation_address(generation, chunk_index, offset), log_header.log_length);
|
||||
offset += log_header.log_length;
|
||||
|
||||
int retries = 3;
|
||||
while (true) {
|
||||
--retries;
|
||||
if (cb(read_buffer, log_header.log_length)) {
|
||||
break;
|
||||
} else if (retries == 0) {
|
||||
error = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
kernel_free(read_buffer);
|
||||
return !error;
|
||||
}
|
||||
|
||||
bool flash_dump_log_file(int generation, DumpLineCallback cb, DumpCompletedCallback completed_cb) {
|
||||
if (generation < 0 || generation >= DEBUG_DB_NUM_FILES) {
|
||||
completed_cb(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!debug_db_is_generation_valid(generation)) {
|
||||
completed_cb(false);
|
||||
return (false);
|
||||
}
|
||||
|
||||
int lowest_chunk_index = 0;
|
||||
uint8_t lowest_chunk_id = 0;
|
||||
int num_valid_chunks = 0;
|
||||
|
||||
for (int i = 0; i < NUM_CHUNKS; ++i) {
|
||||
LogChunkHeader chunk_header;
|
||||
flash_read_bytes((uint8_t*) &chunk_header, get_generation_address(generation, i, 0), sizeof(chunk_header));
|
||||
|
||||
if (chunk_header.invalid || !chunk_header.valid) {
|
||||
// No more valid chunks
|
||||
break;
|
||||
}
|
||||
|
||||
if (i == 0 || serial_distance(lowest_chunk_id, chunk_header.chunk_id, CHUNK_ID_BIT_WIDTH) < 0) {
|
||||
lowest_chunk_index = i;
|
||||
lowest_chunk_id = chunk_header.chunk_id;
|
||||
}
|
||||
|
||||
++num_valid_chunks;
|
||||
}
|
||||
|
||||
bool success = true;
|
||||
for (int i = 0; success && i < num_valid_chunks; ++i) {
|
||||
success = dump_chunk(generation, lowest_chunk_index, cb);
|
||||
lowest_chunk_index = (lowest_chunk_index + 1) % NUM_CHUNKS;
|
||||
}
|
||||
|
||||
completed_cb(num_valid_chunks != 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
void flash_logging_set_enabled(bool enabled) {
|
||||
s_enabled = enabled;
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue