| /* |
| * 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; |
| } |