core/logging/logging_flash.c (342 lines of code) (raw):
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
#include <stdbool.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include "logging_flash.h"
/**
* Length indicator bit for a termination entry.
*/
#define LOGGING_FLASH_TERMINATOR (1U << 15)
/**
* Save the entry buffer to flash.
*
* @param logging The log that should be saved.
*
* @return 0 if the data was successfully saved or an error code.
*/
static int logging_flash_save_buffer (const struct logging_flash *logging)
{
size_t write_len;
uint8_t curr_sector_num;
int status = 0;
if (logging->state->next_write != logging->state->entry_buffer) {
write_len = logging->state->next_write - logging->state->entry_buffer;
curr_sector_num = (FLASH_SECTOR_BASE (logging->state->next_addr) - logging->base_addr) /
FLASH_SECTOR_SIZE;
if (FLASH_SECTOR_OFFSET (logging->state->next_addr) == 0) {
status = spi_flash_sector_erase (logging->flash, logging->state->next_addr);
if (status != 0) {
return status;
}
logging->state->flash_used[curr_sector_num] = 0;
if (logging->state->log_start == curr_sector_num) {
int next_sector = (logging->state->log_start + 1) % LOGGING_FLASH_SECTORS;
if (logging->state->flash_used[next_sector] != 0) {
logging->state->log_start = next_sector;
}
}
}
status = spi_flash_write (logging->flash, logging->state->next_addr,
logging->state->entry_buffer, write_len);
if (ROT_IS_ERROR (status)) {
return status;
}
else if (status != (int) write_len) {
write_len = status;
status = LOGGING_INCOMPLETE_FLUSH;
}
else {
status = 0;
}
logging->state->next_addr += write_len;
logging->state->flash_used[curr_sector_num] += write_len;
if (status == 0) {
if ((FLASH_SECTOR_OFFSET (logging->state->next_addr) != 0) &&
((logging->state->write_remain < (int) sizeof (struct logging_entry_header)) ||
logging->state->terminated)) {
logging->state->next_addr =
FLASH_SECTOR_BASE (logging->state->next_addr) + FLASH_SECTOR_SIZE;
}
if (logging->state->next_addr >= (logging->base_addr + LOGGING_FLASH_AREA_LEN)) {
logging->state->next_addr = logging->base_addr;
}
logging->state->next_write = logging->state->entry_buffer;
logging->state->write_remain = sizeof (logging->state->entry_buffer) -
FLASH_SECTOR_OFFSET (logging->state->next_addr);
if (logging->state->terminated) {
logging->state->flash_used[curr_sector_num] -= sizeof (struct logging_entry_header);
logging->state->terminated = false;
}
}
else {
/* The write was not fully complete, so move the remaining data to be at the beginning
* of the buffer. This will ensure it gets written on the next flush. */
memmove (logging->state->entry_buffer, &logging->state->entry_buffer[write_len],
logging->state->next_write - logging->state->entry_buffer - write_len);
logging->state->next_write -= write_len;
}
}
return status;
}
/**
* Write an entry header to the entry buffer. It assumed there is sufficient space for the header.
*
* @param logging The log to update.
* @param length The length of the entry, not including the entry header.
* @param id The entry ID.
*/
static void logging_flash_write_header (const struct logging_flash *logging, uint16_t length,
uint32_t id)
{
struct logging_entry_header header;
header.log_magic = LOGGING_MAGIC_START;
header.length = length + sizeof (header);
header.entry_id = id;
memcpy (logging->state->next_write, (uint8_t*) &header, sizeof (header));
logging->state->next_write += sizeof (header);
logging->state->write_remain -= sizeof (header);
}
int logging_flash_create_entry (const struct logging *logging, uint8_t *entry, size_t length)
{
const struct logging_flash *flash_log = (const struct logging_flash*) logging;
int status;
if ((flash_log == NULL) || (entry == NULL)) {
return LOGGING_INVALID_ARGUMENT;
}
if ((length == 0) ||
((length + sizeof (struct logging_entry_header) >
sizeof (flash_log->state->entry_buffer)))) {
return LOGGING_BAD_ENTRY_LENGTH;
}
platform_mutex_lock (&flash_log->state->lock);
if (flash_log->state->terminated ||
(flash_log->state->write_remain < (int) (sizeof (struct logging_entry_header) + length))) {
if (!flash_log->state->terminated &&
(flash_log->state->write_remain >= (int) sizeof (struct logging_entry_header))) {
logging_flash_write_header (flash_log, LOGGING_FLASH_TERMINATOR, 0);
flash_log->state->terminated = true;
}
status = logging_flash_save_buffer (flash_log);
if (status != 0) {
platform_mutex_unlock (&flash_log->state->lock);
return status;
}
}
logging_flash_write_header (flash_log, length, flash_log->state->next_entry_id++);
memcpy (flash_log->state->next_write, entry, length);
flash_log->state->next_write += length;
flash_log->state->write_remain -= length;
platform_mutex_unlock (&flash_log->state->lock);
return 0;
}
int logging_flash_flush (const struct logging *logging)
{
const struct logging_flash *flash_log = (const struct logging_flash*) logging;
int status;
if (flash_log == NULL) {
return LOGGING_INVALID_ARGUMENT;
}
platform_mutex_lock (&flash_log->state->lock);
status = logging_flash_save_buffer (flash_log);
platform_mutex_unlock (&flash_log->state->lock);
return status;
}
int logging_flash_clear (const struct logging *logging)
{
const struct logging_flash *flash_log = (const struct logging_flash*) logging;
int status;
if (flash_log == NULL) {
return LOGGING_INVALID_ARGUMENT;
}
platform_mutex_lock (&flash_log->state->lock);
status = spi_flash_block_erase (flash_log->flash, flash_log->base_addr);
if (status != 0) {
goto exit;
}
memset (flash_log->state->flash_used, 0, sizeof (flash_log->state->flash_used));
flash_log->state->log_start = 0;
flash_log->state->next_addr = flash_log->base_addr;
flash_log->state->next_write = flash_log->state->entry_buffer;
flash_log->state->write_remain = sizeof (flash_log->state->entry_buffer);
flash_log->state->terminated = false;
exit:
platform_mutex_unlock (&flash_log->state->lock);
return status;
}
int logging_flash_get_size (const struct logging *logging)
{
const struct logging_flash *flash_log = (const struct logging_flash*) logging;
int sector;
int log_size = 0;
if (flash_log == NULL) {
return LOGGING_INVALID_ARGUMENT;
}
platform_mutex_lock (&flash_log->state->lock);
for (sector = 0; sector < LOGGING_FLASH_SECTORS; ++sector) {
log_size += flash_log->state->flash_used[sector];
}
log_size += (flash_log->state->next_write - flash_log->state->entry_buffer);
if (flash_log->state->terminated) {
log_size -= sizeof (struct logging_entry_header);
}
platform_mutex_unlock (&flash_log->state->lock);
return log_size;
}
int logging_flash_read_contents (const struct logging *logging, uint32_t offset, uint8_t *contents,
size_t length)
{
const struct logging_flash *flash_log = (const struct logging_flash*) logging;
int bytes_read = 0;
int i;
int sectors;
size_t read_len;
uint32_t read_offset;
int status;
if ((flash_log == NULL) || (contents == NULL)) {
return LOGGING_INVALID_ARGUMENT;
}
platform_mutex_lock (&flash_log->state->lock);
i = flash_log->state->log_start;
sectors = 0;
while ((length != 0) && (sectors < LOGGING_FLASH_SECTORS) &&
(flash_log->state->flash_used[i] != 0)) {
read_offset = (offset < flash_log->state->flash_used[i]) ?
offset : flash_log->state->flash_used[i];
read_len = (length < (flash_log->state->flash_used[i] - read_offset)) ?
length : (flash_log->state->flash_used[i] - read_offset);
if (read_len != 0) {
status = spi_flash_read (flash_log->flash,
flash_log->base_addr + (FLASH_SECTOR_SIZE * i) + read_offset, contents, read_len);
if (status != 0) {
platform_mutex_unlock (&flash_log->state->lock);
return status;
}
}
bytes_read += read_len;
contents += read_len;
length -= read_len;
offset -= read_offset;
i = (i + 1) % LOGGING_FLASH_SECTORS;
sectors++;
}
/* After reading all data from flash, read buffered entries that haven't been flushed yet. */
read_len = flash_log->state->next_write - flash_log->state->entry_buffer;
if (flash_log->state->terminated) {
read_len -= sizeof (struct logging_entry_header);
}
read_offset = (offset < read_len) ? offset : read_len;
read_len = (length < (read_len - read_offset)) ? length : (read_len - read_offset);
memcpy (contents, flash_log->state->entry_buffer + read_offset, read_len);
bytes_read += read_len;
platform_mutex_unlock (&flash_log->state->lock);
return bytes_read;
}
/**
* Initialize a log that uses flash for persistent storage. Log entries already on flash will be
* detected and maintained.
*
* The log will consume an entire flash erase block.
*
* @param logging The log to initialize.
* @param state Variable context for the log. This must be uninitialized.
* @param flash The flash device where log entries are stored.
* @param base_addr The starting address for log entries. This must be aligned to the beginning of
* an erase block.
*
* @return 0 if the log was successfully initialized or an error code.
*/
int logging_flash_init (struct logging_flash *logging, struct logging_flash_state *state,
const struct spi_flash *flash, uint32_t base_addr)
{
if ((logging == NULL) || (flash == NULL) || (state == NULL)) {
return LOGGING_INVALID_ARGUMENT;
}
memset (logging, 0, sizeof (struct logging_flash));
logging->base.create_entry = logging_flash_create_entry;
#ifndef LOGGING_DISABLE_FLUSH
logging->base.flush = logging_flash_flush;
#endif
logging->base.clear = logging_flash_clear;
logging->base.get_size = logging_flash_get_size;
logging->base.read_contents = logging_flash_read_contents;
logging->state = state;
logging->flash = flash;
logging->base_addr = base_addr;
return logging_flash_init_state (logging);
}
/**
* Initialize only the variable state for log in SPI flash. The rest of the log instance is assumed
* to have already been initialized.
*
* This would generally be used with a statically initialized instance.
*
* @param logging The log instance that contains the state to initialize.
*
* @return 0 if the state was successfully initialized or an error code.
*/
int logging_flash_init_state (const struct logging_flash *logging)
{
int curr_sector_num;
uint8_t *pos;
uint8_t *end;
uint32_t entry_id = 0;
uint32_t prev_entry_id = 0;
uint32_t flash_addr;
int found_next = 0;
int status;
if ((logging == NULL) || (logging->state == NULL) || (logging->flash == NULL)) {
return LOGGING_INVALID_ARGUMENT;
}
if (FLASH_BLOCK_BASE (logging->base_addr) != logging->base_addr) {
return LOGGING_STORAGE_NOT_ALIGNED;
}
memset (logging->state, 0, sizeof (struct logging_flash_state));
flash_addr = logging->base_addr;
end = logging->state->entry_buffer + sizeof (logging->state->entry_buffer);
for (curr_sector_num = 0; curr_sector_num < LOGGING_FLASH_SECTORS; ++curr_sector_num) {
status = spi_flash_read (logging->flash,
logging->base_addr + (FLASH_SECTOR_SIZE * curr_sector_num),
logging->state->entry_buffer, sizeof (logging->state->entry_buffer));
if (status != 0) {
return status;
}
pos = logging->state->entry_buffer;
while ((end - pos) >= (int) sizeof (struct logging_entry_header)) {
struct logging_entry_header *header = (struct logging_entry_header*) pos;
if (!LOGGING_IS_ENTRY_START (header->log_magic) ||
(LOGGING_HEADER_FORMAT (header->log_magic) == 0xA)) {
if (found_next == 0) {
if (FLASH_SECTOR_OFFSET (flash_addr) == 0) {
found_next = 1;
}
else {
bool blank = true;
while (pos != end) {
if (*pos != 0xff) {
blank = false;
break;
}
pos++;
}
if (!blank) {
flash_addr = FLASH_SECTOR_BASE (flash_addr) + FLASH_SECTOR_SIZE;
}
else {
found_next = 1;
}
}
}
break;
}
else {
int length = header->length & ~LOGGING_FLASH_TERMINATOR;
if ((length > (end - pos)) ||
(length < (int) sizeof (struct logging_entry_header))) {
if (found_next == 0) {
flash_addr = FLASH_SECTOR_BASE (flash_addr) + FLASH_SECTOR_SIZE;
}
break;
}
if (header->length & LOGGING_FLASH_TERMINATOR) {
if (found_next == 0) {
flash_addr = FLASH_SECTOR_BASE (flash_addr) + FLASH_SECTOR_SIZE;
}
break;
}
if (found_next < 2) {
entry_id = header->entry_id + 1;
if (prev_entry_id > entry_id) {
entry_id = prev_entry_id;
logging->state->log_start = curr_sector_num;
found_next = 2;
}
else if (found_next == 0) {
flash_addr += length;
prev_entry_id = entry_id;
}
}
logging->state->flash_used[curr_sector_num] += length;
pos += length;
}
}
if ((FLASH_SECTOR_SIZE - FLASH_SECTOR_OFFSET (flash_addr)) <
sizeof (struct logging_entry_header)) {
flash_addr = FLASH_SECTOR_BASE (flash_addr) + FLASH_SECTOR_SIZE;
}
}
if (flash_addr >= (logging->base_addr + LOGGING_FLASH_AREA_LEN)) {
flash_addr = logging->base_addr;
}
status = platform_mutex_init (&logging->state->lock);
if (status != 0) {
return status;
}
logging->state->next_addr = flash_addr;
logging->state->next_entry_id = entry_id;
logging->state->next_write = logging->state->entry_buffer;
logging->state->write_remain =
sizeof (logging->state->entry_buffer) - FLASH_SECTOR_OFFSET (flash_addr);
return 0;
}
/**
* Release the resources used by a flash logger. The contents on flash will remain. Any entries
* not already on flash will be lost.
*
* @param logging The log to release.
*/
void logging_flash_release (const struct logging_flash *logging)
{
if (logging) {
platform_mutex_free (&logging->state->lock);
}
}