blob: 3587bc94d6937426393d25fdd28ad1e71470a6c3 [file] [log] [blame]
/*
* Copyright (c) 2017-2020 Nordic Semiconductor ASA
* Copyright (c) 2015 Runtime Inc
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "fcb_priv.h"
#include <errno.h>
#include <limits.h>
#include <stdlib.h>
#include <string.h>
#include <zephyr/device.h>
#include <zephyr/drivers/flash.h>
#include <zephyr/fs/fcb.h>
uint8_t fcb_get_align(const struct fcb *fcbp)
{
uint8_t align;
if (fcbp->fap == NULL) {
return 0;
}
align = flash_area_align(fcbp->fap);
return align;
}
int fcb_flash_read(const struct fcb *fcbp, const struct flash_sector *sector, off_t off,
void *dst, size_t len)
{
int rc;
if (off + len > sector->fs_size) {
return -EINVAL;
}
if (fcbp->fap == NULL) {
return -EIO;
}
rc = flash_area_read(fcbp->fap, sector->fs_off + off, dst, len);
if (rc != 0) {
return -EIO;
}
return 0;
}
int fcb_flash_write(const struct fcb *fcbp, const struct flash_sector *sector, off_t off,
const void *src, size_t len)
{
int rc;
if (off + len > sector->fs_size) {
return -EINVAL;
}
if (fcbp->fap == NULL) {
return -EIO;
}
rc = flash_area_write(fcbp->fap, sector->fs_off + off, src, len);
if (rc != 0) {
return -EIO;
}
return 0;
}
int fcb_erase_sector(const struct fcb *fcbp, const struct flash_sector *sector)
{
int rc;
if (fcbp->fap == NULL) {
return -EIO;
}
rc = flash_area_flatten(fcbp->fap, sector->fs_off, sector->fs_size);
if (rc != 0) {
return -EIO;
}
return 0;
}
int fcb_init(int f_area_id, struct fcb *fcbp)
{
struct flash_sector *sector;
int rc;
int i;
uint8_t align;
int oldest = -1, newest = -1;
struct flash_sector *oldest_sector = NULL, *newest_sector = NULL;
struct fcb_disk_area fda;
const struct flash_parameters *fparam;
if (!fcbp->f_sectors || fcbp->f_sector_cnt - fcbp->f_scratch_cnt < 1) {
return -EINVAL;
}
rc = flash_area_open(f_area_id, &fcbp->fap);
if (rc != 0) {
return -EINVAL;
}
fparam = flash_get_parameters(fcbp->fap->fa_dev);
fcbp->f_erase_value = fparam->erase_value;
align = fcb_get_align(fcbp);
if (align == 0U) {
return -EINVAL;
}
/* Fill last used, first used */
for (i = 0; i < fcbp->f_sector_cnt; i++) {
sector = &fcbp->f_sectors[i];
rc = fcb_sector_hdr_read(fcbp, sector, &fda);
if (rc < 0) {
return rc;
}
if (rc == 0) {
continue;
}
if (oldest < 0) {
oldest = newest = fda.fd_id;
oldest_sector = newest_sector = sector;
continue;
}
if (FCB_ID_GT(fda.fd_id, newest)) {
newest = fda.fd_id;
newest_sector = sector;
} else if (FCB_ID_GT(oldest, fda.fd_id)) {
oldest = fda.fd_id;
oldest_sector = sector;
}
}
if (oldest < 0) {
/*
* No initialized areas.
*/
oldest_sector = newest_sector = &fcbp->f_sectors[0];
rc = fcb_sector_hdr_init(fcbp, oldest_sector, 0);
if (rc) {
return rc;
}
newest = oldest = 0;
}
fcbp->f_align = align;
fcbp->f_oldest = oldest_sector;
fcbp->f_active.fe_sector = newest_sector;
fcbp->f_active.fe_elem_off = fcb_len_in_flash(fcbp, sizeof(struct fcb_disk_area));
fcbp->f_active_id = newest;
while (1) {
rc = fcb_getnext_in_sector(fcbp, &fcbp->f_active);
if (rc == -ENOTSUP) {
rc = 0;
break;
}
if (rc != 0) {
break;
}
}
k_mutex_init(&fcbp->f_mtx);
return rc;
}
int fcb_free_sector_cnt(struct fcb *fcbp)
{
int i;
struct flash_sector *fa;
fa = fcbp->f_active.fe_sector;
for (i = 0; i < fcbp->f_sector_cnt; i++) {
fa = fcb_getnext_sector(fcbp, fa);
if (fa == fcbp->f_oldest) {
break;
}
}
return i;
}
int fcb_is_empty(struct fcb *fcbp)
{
return (fcbp->f_active.fe_sector == fcbp->f_oldest &&
fcbp->f_active.fe_elem_off == fcb_len_in_flash(fcbp, sizeof(struct fcb_disk_area)));
}
/**
* Length of an element is encoded in 1 or 2 bytes.
* 1 byte for lengths < 128 bytes, 2 bytes for < 16384.
*
* The storage of length has been originally designed to work with 0xff erasable
* flash devices and gives length 0xffff special meaning: that there is no value
* written; this is smart way to utilize value in non-written flash to figure
* out where data ends. Additionally it sets highest bit of first byte of
* the length to 1, to mark that there is second byte to be read.
* Above poses some problems when non-0xff erasable flash is used. To solve
* the problem all length values are xored with not of erase value for given
* flash:
* len' = len ^ ~erase_value;
* To obtain original value, the logic is reversed:
* len = len' ^ ~erase_value;
*
* In case of 0xff erased flash this does not modify data that is written to
* flash; in case of other flash devices, e.g. that erase to 0x00, it allows
* to correctly use the first bit of byte to figure out how many bytes are there
* and if there is any data at all or both bytes are equal to erase value.
*/
int fcb_put_len(const struct fcb *fcbp, uint8_t *buf, uint16_t len)
{
if (len < 0x80) {
buf[0] = len ^ ~fcbp->f_erase_value;
return 1;
} else if (len <= FCB_MAX_LEN) {
buf[0] = (len | 0x80) ^ ~fcbp->f_erase_value;
buf[1] = (len >> 7) ^ ~fcbp->f_erase_value;
return 2;
} else {
return -EINVAL;
}
}
int fcb_get_len(const struct fcb *fcbp, uint8_t *buf, uint16_t *len)
{
int rc;
uint8_t buf0_xor;
uint8_t buf1_xor;
buf0_xor = buf[0] ^ ~fcbp->f_erase_value;
if (buf0_xor & 0x80) {
if ((buf[0] == fcbp->f_erase_value) && (buf[1] == fcbp->f_erase_value)) {
return -ENOTSUP;
}
buf1_xor = buf[1] ^ ~fcbp->f_erase_value;
*len = (uint16_t)((buf0_xor & 0x7f) | ((uint16_t)buf1_xor << 7));
rc = 2;
} else {
*len = (uint16_t)(buf0_xor);
rc = 1;
}
return rc;
}
/**
* Initialize erased sector for use.
*/
int fcb_sector_hdr_init(struct fcb *fcbp, struct flash_sector *sector, uint16_t id)
{
struct fcb_disk_area fda;
int rc;
fda.fd_magic = fcb_flash_magic(fcbp);
fda.fd_ver = fcbp->f_version;
fda._pad = fcbp->f_erase_value;
fda.fd_id = id;
rc = fcb_flash_write(fcbp, sector, 0, &fda, sizeof(fda));
if (rc != 0) {
return -EIO;
}
return 0;
}
/**
* Checks whether FCB sector contains data or not.
* Returns <0 in error.
* Returns 0 if sector is unused;
* Returns 1 if sector has data.
*/
int fcb_sector_hdr_read(struct fcb *fcbp, struct flash_sector *sector, struct fcb_disk_area *fdap)
{
struct fcb_disk_area fda;
int rc;
if (!fdap) {
fdap = &fda;
}
rc = fcb_flash_read(fcbp, sector, 0, fdap, sizeof(*fdap));
if (rc) {
return -EIO;
}
if (fdap->fd_magic == MK32(fcbp->f_erase_value)) {
return 0;
}
if (fdap->fd_magic != fcb_flash_magic(fcbp)) {
return -ENOMSG;
}
return 1;
}
/**
* Finds the fcb entry that gives back upto n entries at the end.
* @param0 ptr to fcb
* @param1 n number of fcb entries the user wants to get
* @param2 ptr to the fcb_entry to be returned
* @return 0 on there are any fcbs aviable; -ENOENT otherwise
*/
int fcb_offset_last_n(struct fcb *fcbp, uint8_t entries, struct fcb_entry *last_n_entry)
{
struct fcb_entry loc;
int i;
int rc;
/* assure a minimum amount of entries */
if (!entries) {
entries = 1U;
}
i = 0;
(void)memset(&loc, 0, sizeof(loc));
while (!fcb_getnext(fcbp, &loc)) {
if (i == 0) {
/* Start from the beginning of fcb entries */
*last_n_entry = loc;
}
/* Update last_n_entry after n entries and keep updating */
else if (i > (entries - 1)) {
rc = fcb_getnext(fcbp, last_n_entry);
if (rc) {
/* A fcb history must have been erased,
* wanted entry doesn't exist anymore.
*/
return -ENOENT;
}
}
i++;
}
return (i == 0) ? -ENOENT : 0;
}
/**
* Clear fcb
* @param fcb
* @return 0 on success; non-zero on failure
*/
int fcb_clear(struct fcb *fcbp)
{
int rc;
rc = 0;
while (!fcb_is_empty(fcbp)) {
rc = fcb_rotate(fcbp);
if (rc) {
break;
}
}
return rc;
}