blob: cf4850de1a383bd815ba27ec3b2da15bbd3d6f0d [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 "os/mynewt.h"
#if MYNEWT_VAL(LOG_FCB)
#include <string.h>
#include "flash_map/flash_map.h"
#include "fcb/fcb.h"
#include "log/log.h"
/* Assume the flash alignment requirement is no stricter than 32. */
#define LOG_FCB_MAX_ALIGN 32
#define LOG_FCB_EXT_HDR_SIZE LOG_BASE_ENTRY_HDR_SIZE + LOG_IMG_HASHLEN + \
LOG_FCB_MAX_ALIGN
/* Assuming the trailer fits in this, an arbitrary value */
#define LOG_FCB_FLAT_BUF_SIZE (LOG_FCB_EXT_HDR_SIZE > LOG_FCB_MAX_ALIGN * 2) ? \
LOG_FCB_EXT_HDR_SIZE : LOG_FCB_MAX_ALIGN * 2
static int log_fcb_rtr_erase(struct log *log);
static int
fcb_get_fa_hdr(struct fcb *fcb, struct log *log, struct fcb_entry *fcb_entry, struct log_entry_hdr *hdr)
{
int rc;
rc = fcb_getnext(fcb, fcb_entry);
if (rc == 0) {
return log_read_hdr(log, fcb_entry, hdr);
} else {
return rc;
}
}
/**
* Helper function to find start point for walking, given an offset.
* for a non-zero offset, instead of walking from the beginning,
* walk from the last applicable area, check first entry and compare
* against the given offset. Repeat this until an offset less than the
* given offset is found, start walking from there.
*/
static int
fcb_walk_back_find_start(struct fcb *fcb, struct log *log,
struct log_offset *log_offset,
struct fcb_entry *fcb_entry)
{
struct flash_area *fap;
struct log_entry_hdr hdr;
struct fcb_entry iter_entry = {0};
int rc;
/**
* If the provided lo_index is less than the oldest entry,
* simply return the oldest. Else, start from the active area and search back.
*/
iter_entry.fe_area = fcb->f_oldest;
rc = fcb_get_fa_hdr(fcb, log, &iter_entry, &hdr);
if (rc != 0) {
return rc;
}
if (hdr.ue_index >= log_offset->lo_index) {
goto found_ent;
}
fap = fcb->f_active.fe_area;
for (hdr.ue_index = log_offset->lo_index; hdr.ue_index >= log_offset->lo_index;) {
memset(&iter_entry, 0, sizeof(iter_entry));
iter_entry.fe_area = fap;
rc = fcb_get_fa_hdr(fcb, log, &iter_entry, &hdr);
if (rc != 0) {
return rc;
}
/* Check if about to wrap around */
if (fap == &fcb->f_sectors[0]) {
fap = &(fcb->f_sectors[fcb->f_sector_cnt-1]);
} else {
fap--;
}
}
found_ent:
/* copy the result back to caller */
memcpy(fcb_entry, &iter_entry, sizeof(struct fcb_entry));
return 0;
}
/**
* Finds the first log entry whose "offset" is >= the one specified. A log
* offset consists of two parts:
* o timestamp
* o index
*
* The "timestamp" field is misnamed. If it has a value of -1, then the offset
* always points to the latest entry. If this value is not -1, then it is
* ignored; the "index" field is used instead.
*
* XXX: We should rename "timestamp" or make it an actual timestamp.
*
* The "index" field corresponds to a log entry index.
*
* If bookmark is found with the minimum difference in indices, min_diff contains
* the difference else it will contain -1 if no bookmark is found. min_diff is 0
* means an exact match.
*
* If bookmarks are enabled, this function uses them in the search.
*
* @return 0 if an entry was found
* SYS_ENOENT if there are no suitable entries.
* Other error on failure.
*/
static int
log_fcb_find_gte(struct log *log, struct log_offset *log_offset,
struct fcb_entry *out_entry, int *min_diff)
{
#if MYNEWT_VAL(LOG_FCB_BOOKMARKS)
const struct log_fcb_bmark *bmark;
#endif
struct log_entry_hdr hdr;
struct fcb_log *fcb_log;
struct fcb *fcb;
int rc;
bool bmark_found = false;
fcb_log = log->l_arg;
fcb = &fcb_log->fl_fcb;
/* Attempt to read the first entry. If this fails, the FCB is empty. */
memset(out_entry, 0, sizeof *out_entry);
if (log_offset->lo_ts < 0) {
out_entry->fe_step_back = true;
}
rc = fcb_getnext(fcb, out_entry);
if (rc == FCB_ERR_NOVAR) {
return SYS_ENOENT;
} else if (rc != 0) {
return SYS_EUNKNOWN;
}
/*
* if timestamp for request is < 0, return last log entry
*/
if (log_offset->lo_ts < 0) {
*out_entry = fcb->f_active;
out_entry->fe_step_back = true;
return 0;
}
/* If the requested index is beyond the end of the log, there is nothing to
* retrieve.
*/
rc = log_read_hdr(log, &fcb->f_active, &hdr);
if (rc != 0) {
return rc;
}
if (log_offset->lo_index > hdr.ue_index) {
return SYS_ENOENT;
}
#if MYNEWT_VAL(LOG_FCB_BOOKMARKS)
bmark = log_fcb_closest_bmark(fcb_log, log_offset->lo_index, min_diff);
if (bmark != NULL) {
*out_entry = bmark->lfb_entry;
bmark_found = true;
}
#endif
/**
* For non-zero indices, we walk back from the latest fe_area,
* compare the ue_index with lo_index for the first entry of each
* of these areas. If we find one that is less than the lo_index,
* use that. This covers a case if we are looking for a an entry
* GTE to any random non-zero value. If bookmark is set, it is expected
* that the log is walked from there.
*/
if ((bmark_found == false) && (log_offset->lo_index != 0)) {
rc = fcb_walk_back_find_start(fcb, log, log_offset, out_entry);
if (rc != 0) {
return rc;
}
}
do {
rc = log_read_hdr(log, out_entry, &hdr);
if (rc != 0) {
return rc;
}
if (hdr.ue_index >= log_offset->lo_index) {
return 0;
}
} while (fcb_getnext(fcb, out_entry) == 0);
return SYS_ENOENT;
}
static int
log_fcb_start_append(struct log *log, int len, struct fcb_entry *loc)
{
struct fcb_log *fcb_log = (struct fcb_log *)log->l_arg;
struct fcb *fcb = &fcb_log->fl_fcb;
struct flash_area *old_fa;
int rc = 0;
#if MYNEWT_VAL(LOG_FCB_SECTOR_BOOKMARKS)
int active_id;
uint32_t idx;
struct log_fcb_bset *bset = &fcb_log->fl_bset;
#endif
#if MYNEWT_VAL(LOG_STATS)
int cnt;
#endif
/* Cache active ID before appending */
#if MYNEWT_VAL(LOG_FCB_SECTOR_BOOKMARKS)
active_id = fcb->f_active_id;
#endif
while (1) {
rc = fcb_append(fcb, len, loc);
if (rc == 0) {
break;
}
if (rc != FCB_ERR_NOSPACE) {
goto err;
}
if (fcb_log->fl_entries) {
rc = log_fcb_rtr_erase(log);
if (rc) {
goto err;
}
continue;
}
old_fa = fcb->f_oldest;
(void)old_fa; /* to avoid #ifdefs everywhere... */
#if MYNEWT_VAL(LOG_STATS)
rc = fcb_area_info(fcb, NULL, &cnt, NULL);
if (rc == 0) {
LOG_STATS_INCN(log, lost, cnt);
}
#endif
/* Notify upper layer that a rotation is about to occur */
if (log->l_rotate_notify_cb != NULL) {
fcb_append_to_scratch(fcb);
log->l_rotate_notify_cb(log);
}
#if MYNEWT_VAL(LOG_FCB_BOOKMARKS) && !MYNEWT_VAL(LOG_FCB_SECTOR_BOOKMARKS)
/* The FCB needs to be rotated. For sector bookmarks
* we just re-initialize the bookmarks
*/
log_fcb_rotate_bmarks(fcb_log);
#endif
rc = fcb_rotate(fcb);
if (rc) {
goto err;
}
#if MYNEWT_VAL(LOG_FCB_SECTOR_BOOKMARKS)
/* The FCB needs to be rotated, reinit previously allocated
* bookmarks
*/
log_fcb_init_bmarks(fcb_log, bset->lfs_bmarks, bset->lfs_cap,
bset->lfs_en_sect_bmarks);
#endif
#if MYNEWT_VAL(LOG_STORAGE_WATERMARK)
/*
* FCB was rotated successfully so let's check if watermark was within
* oldest flash area which was erased. If yes, then move watermark to
* beginning of current oldest area.
*/
if ((fcb_log->fl_watermark_off >= old_fa->fa_off) &&
(fcb_log->fl_watermark_off < old_fa->fa_off + old_fa->fa_size)) {
fcb_log->fl_watermark_off = fcb->f_oldest->fa_off;
}
#endif
}
#if MYNEWT_VAL(LOG_FCB_SECTOR_BOOKMARKS)
/* Add bookmark if entry is added to a new sector */
if (!rc && log->l_log->log_type != LOG_TYPE_STREAM) {
/* If sector bookmarks are enabled, add a bookmark if the entry is
* added to a new sector (active_id change) or if the the first sector
* bookmark is not set yet.
*/
if (bset->lfs_en_sect_bmarks &&
(!bset->lfs_bmarks[bset->lfs_sect_cap - 1].lfb_entry.fe_area ||
fcb->f_active_id != active_id)) {
#if MYNEWT_VAL(LOG_GLOBAL_IDX)
idx = g_log_info.li_next_index;
#else
idx = log->l_idx;
#endif
if (!bset->lfs_bmarks[bset->lfs_sect_cap - 1].lfb_entry.fe_area) {
bset->lfs_next_sect = bset->lfs_sect_cap - 1;
}
log_fcb_add_bmark(fcb_log, loc, idx, true);
}
}
#endif
err:
return (rc);
}
static int
log_fcb_hdr_bytes(uint16_t align, uint16_t len)
{
uint16_t mod;
/* Assume power-of-two alignment for faster modulo calculation. */
assert((align & (align - 1)) == 0);
mod = len & (align - 1);
if (mod == 0) {
return 0;
}
return align - mod;
}
static int
log_fcb_append_body(struct log *log, const struct log_entry_hdr *hdr,
const void *body, int body_len)
{
uint8_t buf[LOG_FCB_FLAT_BUF_SIZE] = {0};
struct fcb *fcb;
struct fcb_entry loc = {};
struct fcb_log *fcb_log;
const uint8_t *u8p;
int hdr_alignment;
int chunk_sz = 0;
int rc;
uint16_t hdr_len;
#if MYNEWT_VAL(LOG_FLAGS_TRAILER)
int trailer_alignment = 0;
uint16_t trailer_len = 0;
#endif
uint16_t padding = 0;
uint16_t offset = 0;
fcb_log = (struct fcb_log *)log->l_arg;
fcb = &fcb_log->fl_fcb;
(void)offset;
(void)padding;
if (fcb->f_align > LOG_FCB_MAX_ALIGN) {
return SYS_ENOTSUP;
}
hdr_len = log_hdr_len(hdr);
/* Append the first chunk (header + x-bytes of body, where x is however
* many bytes are required to increase the chunk size up to a multiple of
* the flash alignment). If the hash flag is set, we have to account for
* appending the hash right after the header.
*/
hdr_alignment = log_fcb_hdr_bytes(fcb->f_align, hdr_len);
if (hdr_alignment > body_len) {
chunk_sz = hdr_len + body_len;
} else {
chunk_sz = hdr_len + hdr_alignment;
}
/*
* Based on the flags being set in the log header, we need to write
* specific fields to the flash
*/
u8p = body;
memcpy(buf, hdr, LOG_BASE_ENTRY_HDR_SIZE);
if (hdr->ue_flags & LOG_FLAGS_IMG_HASH) {
memcpy(buf + LOG_BASE_ENTRY_HDR_SIZE, hdr->ue_imghash, LOG_IMG_HASHLEN);
}
memcpy(buf + hdr_len, u8p, hdr_alignment);
#if MYNEWT_VAL(LOG_FLAGS_TRAILER)
if (hdr->ue_flags & LOG_FLAGS_TRAILER && log->l_tr_om) {
/* Calculate trailer alignment */
trailer_alignment = log_fcb_hdr_bytes(fcb->f_align,
chunk_sz + body_len -
hdr_alignment);
/* If trailer is set, we need to write the trailer length and
* trailer data after the body.
* ------------------------------------------------------
* | hdr + align | body | align + trailer | trailer len |
* ------------------------------------------------------
*/
trailer_len = os_mbuf_len(log->l_tr_om);
rc = log_fcb_start_append(log, hdr_len + body_len + trailer_len +
trailer_alignment + LOG_TRAILER_LEN_SIZE,
&loc);
} else
#endif
{
/* If trailer is not set, we just write the header + body */
rc = log_fcb_start_append(log, hdr_len + body_len, &loc);
}
if (rc != 0) {
return rc;
}
rc = fcb_write(fcb, &loc, buf, chunk_sz);
if (rc != 0) {
return rc;
}
/* Append the remainder of the message body. */
u8p += hdr_alignment;
if (body_len > hdr_alignment) {
body_len -= hdr_alignment;
}
#if MYNEWT_VAL(LOG_FLAGS_TRAILER)
if (hdr->ue_flags & LOG_FLAGS_TRAILER && log->l_tr_om) {
memset(buf, 0, sizeof(buf));
padding = trailer_alignment ? fcb->f_align - trailer_alignment : 0;
/* This writes padding + trailer_alignment if needed */
if (body_len > padding) {
/* Writes body - padding bytes */
rc = fcb_write(fcb, &loc, u8p, body_len - padding);
u8p += (body_len - padding);
memcpy(buf, u8p, padding);
offset = padding;
} else {
/* Just write the entire body since its less than padding */
rc = fcb_write(fcb, &loc, u8p, body_len);
u8p += body_len;
}
if (rc != 0) {
return rc;
}
offset += trailer_alignment;
rc = os_mbuf_copydata(log->l_tr_om, 0, trailer_len, buf + offset);
if (rc) {
return rc;
}
offset += trailer_len;
memcpy(buf + offset, &trailer_len, LOG_TRAILER_LEN_SIZE);
offset += LOG_TRAILER_LEN_SIZE;
/* Writes the following:
* ------------------------------------------------------------------
* | body: [padding] | trailer_alignment | trailer | trailer length |
* ------------------------------------------------------------------
*
* Padding is optional based on whether body_len > padding.
*/
rc = fcb_write(fcb, &loc, buf, offset);
} else
#endif
{
if (body_len > 0) {
rc = fcb_write(fcb, &loc, u8p, body_len);
}
}
if (rc != 0) {
return rc;
}
rc = fcb_append_finish(fcb, &loc);
if (rc != 0) {
return rc;
}
return 0;
}
static int
log_fcb_append(struct log *log, void *buf, int len)
{
int hdr_len;
hdr_len = log_hdr_len(buf);
return log_fcb_append_body(log, buf, (uint8_t *)buf + hdr_len,
len - hdr_len);
}
static int
log_fcb_write_mbuf(struct fcb_entry *loc, struct os_mbuf *om)
{
int rc;
while (om) {
rc = flash_area_write(loc->fe_area, loc->fe_data_off, om->om_data,
om->om_len);
if (rc != 0) {
return SYS_EIO;
}
loc->fe_data_off += om->om_len;
om = SLIST_NEXT(om, om_next);
}
return 0;
}
static int
log_fcb_append_mbuf_body(struct log *log, const struct log_entry_hdr *hdr,
struct os_mbuf *om)
{
struct fcb *fcb;
struct fcb_entry loc = {};
struct fcb_log *fcb_log;
int len;
int rc;
uint16_t buflen = 0;
#if MYNEWT_VAL(LOG_FLAGS_TRAILER)
int trailer_alignment = 0;
uint8_t pad[LOG_FCB_MAX_ALIGN] = {0};
uint16_t trailer_len = 0;
#endif
fcb_log = (struct fcb_log *)log->l_arg;
fcb = &fcb_log->fl_fcb;
/* This function expects to be able to write each mbuf without any
* buffering.
*/
if (fcb->f_align != 1) {
return SYS_ENOTSUP;
}
buflen = os_mbuf_len(om);
len = log_hdr_len(hdr) + buflen;
#if MYNEWT_VAL(LOG_FLAGS_TRAILER)
if (hdr->ue_flags & LOG_FLAGS_TRAILER) {
/* The trailer gets appended after the trailer_alignment
* Trailers start from updated loc.fe_data_off. Write everything
* together
* Writes the following:
* -----------------------------------------------------------------
* | body: body_len | trailer_alignment | trailer | trailer length |
* -----------------------------------------------------------------
* part of body len + trailer_alignment = fcb->f_align
* So, we just pad trailer_alignment agt the end of the mbuf chain
*/
if (log->l_tr_om) {
trailer_len = os_mbuf_len(log->l_tr_om);
/* Calculate trailer alignment */
trailer_alignment = log_fcb_hdr_bytes(fcb->f_align, trailer_len);
len += (trailer_alignment + trailer_len + LOG_TRAILER_LEN_SIZE);
}
}
#endif
rc = log_fcb_start_append(log, len, &loc);
if (rc != 0) {
return rc;
}
rc = flash_area_write(loc.fe_area, loc.fe_data_off, hdr,
LOG_BASE_ENTRY_HDR_SIZE);
if (rc != 0) {
return rc;
}
loc.fe_data_off += LOG_BASE_ENTRY_HDR_SIZE;
if (hdr->ue_flags & LOG_FLAGS_IMG_HASH) {
/* Write LOG_IMG_HASHLEN bytes of image hash */
rc = flash_area_write(loc.fe_area, loc.fe_data_off, hdr->ue_imghash,
LOG_IMG_HASHLEN);
if (rc != 0) {
return rc;
}
loc.fe_data_off += LOG_IMG_HASHLEN;
}
#if MYNEWT_VAL(LOG_FLAGS_TRAILER)
if (hdr->ue_flags & LOG_FLAGS_TRAILER) {
/* The trailer gets appended after the padding + trailer_alignment
* Trailers start from updated loc.fe_data_off. Write everything
* together
* Writes the following:
* -----------------------------------------------------------------
* | body: body_len | trailer_alignment | trailer | trailer length |
* -----------------------------------------------------------------
* part of body_len + trailer_alignment = f_align
*/
if (log->l_tr_om) {
if (trailer_alignment) {
/* Copy padding for trailer alignment */
rc = os_mbuf_copyinto(om, buflen, pad, trailer_alignment);
if (rc) {
return rc;
}
}
/* Append from the trailer */
rc = os_mbuf_appendfrom(om, log->l_tr_om, 0, trailer_len);
if (rc) {
return rc;
}
om = os_mbuf_pullup(om, LOG_TRAILER_LEN_SIZE);
/* Copy the trailer length */
rc = os_mbuf_copyinto(om, buflen + trailer_alignment + trailer_len,
&trailer_len, LOG_TRAILER_LEN_SIZE);
if (rc) {
return rc;
}
}
}
#endif
rc = log_fcb_write_mbuf(&loc, om);
if (rc != 0) {
return rc;
}
rc = fcb_append_finish(fcb, &loc);
if (rc != 0) {
return rc;
}
return 0;
}
static int
log_fcb_append_mbuf(struct log *log, struct os_mbuf *om)
{
int rc;
uint16_t mlen;
uint16_t hdr_len;
struct log_entry_hdr hdr;
mlen = os_mbuf_len(om);
if (mlen < LOG_BASE_ENTRY_HDR_SIZE) {
return SYS_ENOMEM;
}
/*
* We do a pull up twice, once so that the base header is
* contiguous, so that we read the flags correctly, second
* time is so that we account for the image hash as well.
*/
om = os_mbuf_pullup(om, LOG_BASE_ENTRY_HDR_SIZE);
/*
* We can just pass the om->om_data ptr as the log_entry_hdr
* because the log_entry_hdr is a packed struct and does not
* cause any alignment or padding issues
*/
hdr_len = log_hdr_len((struct log_entry_hdr *)om->om_data);
om = os_mbuf_pullup(om, hdr_len);
memcpy(&hdr, om->om_data, hdr_len);
os_mbuf_adj(om, hdr_len);
rc = log_fcb_append_mbuf_body(log, &hdr, om);
os_mbuf_prepend(om, hdr_len);
memcpy(om->om_data, &hdr, hdr_len);
return rc;
}
static uint16_t
log_fcb_read_entry_len(struct log *log, const void *dptr)
{
struct fcb_entry *loc;
loc = (struct fcb_entry *)dptr;
if (!log || !dptr) {
return 0;
}
return loc->fe_data_len;
}
static int
log_fcb_read(struct log *log, const void *dptr, void *buf, uint16_t offset,
uint16_t len)
{
struct fcb_entry *loc;
int rc;
loc = (struct fcb_entry *)dptr;
if (offset + len > loc->fe_data_len) {
len = loc->fe_data_len - offset;
}
rc = flash_area_read(loc->fe_area, loc->fe_data_off + offset, buf, len);
if (rc == 0) {
return len;
} else {
return 0;
}
}
static int
log_fcb_read_mbuf(struct log *log, const void *dptr, struct os_mbuf *om,
uint16_t offset, uint16_t len)
{
struct fcb_entry *loc;
uint8_t data[128];
uint16_t read_len;
uint16_t rem_len;
int rc;
loc = (struct fcb_entry *)dptr;
if (offset + len > loc->fe_data_len) {
len = loc->fe_data_len - offset;
}
rem_len = len;
while (rem_len > 0) {
read_len = min(rem_len, sizeof(data));
rc = flash_area_read(loc->fe_area, loc->fe_data_off + offset, data,
read_len);
if (rc) {
goto done;
}
rc = os_mbuf_append(om, data, read_len);
if (rc) {
goto done;
}
rem_len -= read_len;
offset += read_len;
}
done:
return len - rem_len;
}
/**
* @brief Common function for walking a single area or the full logs
*
* @param The log
* @param[in] The walk function
* @param The log offset
* @param[in] Reading either a single area or the full log
*
* @return { description_of_the_return_value }
*/
static int
log_fcb_walk_impl(struct log *log, log_walk_func_t walk_func,
struct log_offset *log_offset, bool area)
{
struct fcb *fcb;
struct fcb_log *fcb_log;
struct fcb_entry loc = {};
struct flash_area *fap;
int rc;
struct fcb_entry_cache cache;
int min_diff = -1;
fcb_log = log->l_arg;
fcb = &fcb_log->fl_fcb;
/* Locate the starting point of the walk. */
rc = log_fcb_find_gte(log, log_offset, &loc, &min_diff);
switch (rc) {
case 0:
/* Found a starting point. */
break;
case SYS_ENOENT:
/* No entries match the offset criteria; nothing to walk. */
return 0;
default:
return rc;
}
fap = loc.fe_area;
if (log_offset->lo_walk_backward) {
loc.fe_step_back = true;
if (MYNEWT_VAL(FCB_BIDIRECTIONAL_CACHE)) {
fcb_cache_init(fcb, &cache, 50);
loc.fe_cache = &cache;
}
}
#if MYNEWT_VAL(LOG_FCB_BOOKMARKS)
/* If a minimum index was specified (i.e., we are not just retrieving the
* last entry), add a bookmark pointing to this walk's start location.
* Only add a bmark if the index is non-zero and an exactly matching bmark
* was not found. If an exactly matching bmark was found, min_diff is 0,
* else it stays -1 or is great than 0.
*/
if ((log_offset->lo_ts >= 0 && log_offset->lo_index > 0) && min_diff != 0) {
log_fcb_add_bmark(fcb_log, &loc, log_offset->lo_index, false);
}
#endif
rc = 0;
do {
if (area) {
if (fap != loc.fe_area) {
break;
}
}
rc = walk_func(log, log_offset, &loc, loc.fe_data_len);
if (rc != 0) {
if (rc > 0) {
rc = 0;
}
break;
}
} while (fcb_getnext(fcb, &loc) == 0);
if (log_offset->lo_walk_backward && MYNEWT_VAL(FCB_BIDIRECTIONAL_CACHE)) {
fcb_cache_free(fcb, &cache);
}
return rc;
}
static int
log_fcb_walk(struct log *log, log_walk_func_t walk_func,
struct log_offset *log_offset)
{
return log_fcb_walk_impl(log, walk_func, log_offset, false);
}
static int
log_fcb_walk_area(struct log *log, log_walk_func_t walk_func,
struct log_offset *log_offset)
{
return log_fcb_walk_impl(log, walk_func, log_offset, true);
}
static int
log_fcb_flush(struct log *log)
{
struct fcb_log *fcb_log;
struct fcb *fcb;
int rc;
fcb_log = (struct fcb_log *)log->l_arg;
fcb = &fcb_log->fl_fcb;
rc = fcb_clear(fcb);
#if MYNEWT_VAL(LOG_FCB_BOOKMARKS)
#if MYNEWT_VAL(LOG_FCB_SECTOR_BOOKMARKS)
/* Reinit previously allocated bookmarks */
log_fcb_init_bmarks(fcb_log, fcb_log->fl_bset.lfs_bmarks,
fcb_log->fl_bset.lfs_cap,
fcb_log->fl_bset.lfs_en_sect_bmarks);
#else
log_fcb_clear_bmarks(fcb_log);
#endif
#endif
return rc;
}
static int
log_fcb_registered(struct log *log)
{
struct fcb_log *fl = (struct fcb_log *)log->l_arg;
fl->fl_log = log;
#if MYNEWT_VAL(LOG_STORAGE_WATERMARK)
#if MYNEWT_VAL(LOG_PERSIST_WATERMARK)
struct fcb *fcb;
struct fcb_entry loc;
#endif
#if MYNEWT_VAL(LOG_PERSIST_WATERMARK)
fcb = &fl->fl_fcb;
/* Set watermark to first element */
memset(&loc, 0, sizeof(loc));
if (fcb_getnext(fcb, &loc)) {
fl->fl_watermark_off = loc.fe_area->fa_off + loc.fe_elem_off;
} else {
fl->fl_watermark_off = fcb->f_oldest->fa_off;
}
#else
/* Initialize watermark to designated unknown value*/
fl->fl_watermark_off = 0xffffffff;
#endif
#endif
return 0;
}
#if MYNEWT_VAL(LOG_STORAGE_INFO)
static int
log_fcb_storage_info(struct log *log, struct log_storage_info *info)
{
struct fcb_log *fl;
struct fcb *fcb;
struct flash_area *fa;
uint32_t el_min;
uint32_t el_max;
uint32_t fa_min;
uint32_t fa_max;
uint32_t fa_size;
uint32_t fa_used;
int rc;
fl = (struct fcb_log *)log->l_arg;
fcb = &fl->fl_fcb;
rc = os_mutex_pend(&fcb->f_mtx, OS_WAIT_FOREVER);
if (rc && rc != OS_NOT_STARTED) {
return FCB_ERR_ARGS;
}
/*
* Calculate location of 1st entry.
* We assume 1st log entry starts at beginning of oldest sector in FCB.
* This is because even if 1st entry is in the middle of sector (is this
* even possible?) we will never use free space before it thus that space
* can be also considered used.
*/
el_min = fcb->f_oldest->fa_off;
/* Calculate end location of last entry */
el_max = fcb->f_active.fe_area->fa_off + fcb->f_active.fe_elem_off;
/* Sectors assigned to FCB are guaranteed to be contiguous */
fa = &fcb->f_sectors[0];
fa_min = fa->fa_off;
fa = &fcb->f_sectors[fcb->f_sector_cnt - 1];
fa_max = fa->fa_off + fa->fa_size;
fa_size = fa_max - fa_min;
/* Calculate used size */
fa_used = el_max - el_min;
if ((int32_t)fa_used < 0) {
fa_used += fa_size;
}
info->size = fa_size;
info->used = fa_used;
#if MYNEWT_VAL(LOG_STORAGE_WATERMARK)
/* Calculate used size */
fa_used = el_max - fl->fl_watermark_off;
if (fl->fl_watermark_off == 0xffffffff){
fa_used = 0xffffffff;
}
else if ((int32_t)fa_used < 0) {
fa_used += fa_size;
}
info->used_unread = fa_used;
#endif
os_mutex_release(&fcb->f_mtx);
return 0;
}
#endif
#if MYNEWT_VAL(LOG_STORAGE_WATERMARK)
static int
log_fcb_new_watermark_index(struct log *log, struct log_offset *log_offset,
const void *dptr, uint16_t len)
{
struct fcb_entry *loc;
struct fcb_log *fl;
struct log_entry_hdr ueh;
int rc;
loc = (struct fcb_entry*)dptr;
fl = (struct fcb_log *)log->l_arg;
rc = log_fcb_read(log, loc, &ueh, 0, sizeof(ueh));
if (rc != sizeof(ueh)) {
return -1;
}
/* Set log watermark to end of this element */
if (ueh.ue_index >= log_offset->lo_index) {
fl->fl_watermark_off = loc->fe_area->fa_off + loc->fe_data_off +
loc->fe_data_len;
return 1;
} else {
return 0;
}
}
static int
log_fcb_set_watermark(struct log *log, uint32_t index)
{
int rc;
struct log_offset log_offset = {};
struct fcb_log *fl;
struct fcb *fcb;
fl = (struct fcb_log *)log->l_arg;
fcb = &fl->fl_fcb;
log_offset.lo_arg = NULL;
log_offset.lo_ts = 0;
log_offset.lo_index = index;
log_offset.lo_data_len = 0;
/* Find where to start the walk, and set watermark accordingly */
rc = log_fcb_walk(log, log_fcb_new_watermark_index, &log_offset);
if (rc != 0) {
goto done;
}
/* If there are no entries to read and the watermark has not been set */
if (fl->fl_watermark_off == 0xffffffff) {
fl->fl_watermark_off = fcb->f_oldest->fa_off;
}
return (0);
done:
return (rc);
}
#endif
/**
* Copies one log entry from source fcb to destination fcb
*
* @param log Log this operation applies to
* @param entry FCB2 location for the entry being copied
* @param dst_fcb FCB2 area where data is getting copied to.
*
* @return 0 on success; non-zero on error
*/
static int
log_fcb_copy_entry(struct log *log, struct fcb_entry *entry,
struct fcb *dst_fcb)
{
struct log_entry_hdr ueh;
char data[MYNEWT_VAL(LOG_FCB_COPY_MAX_ENTRY_LEN) + LOG_BASE_ENTRY_HDR_SIZE +
LOG_IMG_HASHLEN];
uint16_t hdr_len;
int dlen;
int rc;
struct fcb_log fcb_log_tmp;
struct fcb_log *fcb_log_ptr;
rc = log_fcb_read(log, entry, &ueh, 0, LOG_BASE_ENTRY_HDR_SIZE);
if (rc != LOG_BASE_ENTRY_HDR_SIZE) {
goto err;
}
hdr_len = log_hdr_len(&ueh);
dlen = min(entry->fe_data_len, MYNEWT_VAL(LOG_FCB_COPY_MAX_ENTRY_LEN) +
hdr_len);
rc = log_fcb_read(log, entry, data, 0, dlen);
if (rc < 0) {
goto err;
}
/* Cache fcb_log pointer */
fcb_log_ptr = (struct fcb_log *)log->l_arg;
/* Cache the fcb log, so that we preserve original fcb pointer */
fcb_log_tmp = *fcb_log_ptr;
fcb_log_tmp.fl_fcb = *dst_fcb;
log->l_arg = &fcb_log_tmp;
rc = log_fcb_append(log, data, dlen);
/* Restore the original fcb_log pointer */
log->l_arg = fcb_log_ptr;
*dst_fcb = fcb_log_tmp.fl_fcb;
if (rc) {
goto err;
}
err:
return (rc);
}
/**
* Copies log entries from source fcb to destination fcb
*
* @param log Log this operation applies to
* @param src_fcb FCB area which is the source of data
* @param dst_fcb FCB area which is the target
* @param entry Location in src_fcb to start copy from
*
* @return 0 on success; non-zero on error
*/
static int
log_fcb_copy(struct log *log, struct fcb *src_fcb, struct fcb *dst_fcb,
struct fcb_entry *entry)
{
int rc;
rc = 0;
while (!fcb_getnext(src_fcb, entry)) {
rc = log_fcb_copy_entry(log, entry, dst_fcb);
if (rc) {
break;
}
}
return (rc);
}
/**
* Flushes the log while keeping the specified number of entries
* using image scratch
*
* @param log Log this operation applies to
*
* @return 0 on success; non-zero on error
*/
static int
log_fcb_rtr_erase(struct log *log)
{
struct fcb_log *fcb_log;
struct fcb fcb_scratch;
struct fcb *fcb;
const struct flash_area *ptr;
struct fcb_entry entry = {};
int rc;
struct flash_area sector;
rc = 0;
if (!log) {
rc = -1;
goto err;
}
fcb_log = log->l_arg;
fcb = &fcb_log->fl_fcb;
memset(&fcb_scratch, 0, sizeof(fcb_scratch));
if (flash_area_open(FLASH_AREA_IMAGE_SCRATCH, &ptr)) {
goto err;
}
sector = *ptr;
fcb_scratch.f_sectors = &sector;
fcb_scratch.f_sector_cnt = 1;
fcb_scratch.f_magic = 0x7EADBADF;
fcb_scratch.f_version = g_log_info.li_version;
flash_area_erase(&sector, 0, sector.fa_size);
rc = fcb_init(&fcb_scratch);
if (rc) {
goto err;
}
/* Calculate offset of n-th last entry */
rc = fcb_offset_last_n(fcb, fcb_log->fl_entries, &entry);
if (rc) {
goto err;
}
/* Copy to scratch */
rc = log_fcb_copy(log, fcb, &fcb_scratch, &entry);
if (rc) {
goto err;
}
/* Flush log */
rc = log_fcb_flush(log);
if (rc) {
goto err;
}
memset(&entry, 0, sizeof(entry));
/* Copy back from scratch */
rc = log_fcb_copy(log, &fcb_scratch, fcb, &entry);
err:
return (rc);
}
const struct log_handler log_fcb_handler = {
.log_type = LOG_TYPE_STORAGE,
.log_read = log_fcb_read,
.log_read_mbuf = log_fcb_read_mbuf,
.log_append = log_fcb_append,
.log_append_body = log_fcb_append_body,
.log_append_mbuf = log_fcb_append_mbuf,
.log_append_mbuf_body = log_fcb_append_mbuf_body,
.log_walk = log_fcb_walk,
.log_walk_sector = log_fcb_walk_area,
.log_flush = log_fcb_flush,
.log_read_entry_len = log_fcb_read_entry_len,
#if MYNEWT_VAL(LOG_STORAGE_INFO)
.log_storage_info = log_fcb_storage_info,
#endif
#if MYNEWT_VAL(LOG_STORAGE_WATERMARK)
.log_set_watermark = log_fcb_set_watermark,
#endif
.log_registered = log_fcb_registered,
};
#endif