diff --git a/sys/eepreg/Makefile b/sys/eepreg/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..48422e909a47d7cd428d10fa73825060ccc8d8c2 --- /dev/null +++ b/sys/eepreg/Makefile @@ -0,0 +1 @@ +include $(RIOTBASE)/Makefile.base diff --git a/sys/eepreg/eepreg.c b/sys/eepreg/eepreg.c new file mode 100644 index 0000000000000000000000000000000000000000..43d63059164e488a5574b6d0e9ff9772f349a2b4 --- /dev/null +++ b/sys/eepreg/eepreg.c @@ -0,0 +1,498 @@ +/* + * Copyright (C) 2018 Acutam Automation, LLC + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup sys_eepreg + * @{ + * + * @file + * @brief eepreg implementation + * + * @author Matthew Blue <matthew.blue.neuro@gmail.com> + * @} + */ + +#include <errno.h> +#include <limits.h> +#include <stdint.h> +#include <string.h> + +#include "eepreg.h" +#include "periph/eeprom.h" + +#define ENABLE_DEBUG 0 +#include "debug.h" + +/* EEPREG magic number */ +static const char eepreg_magic[] = "RIOTREG"; + +/* constant lengths */ +#define MAGIC_SIZE (sizeof(eepreg_magic) - 1) /* -1 to remove null */ +#define ENT_LEN_SIZ (1U) + +/* constant locations */ +#define REG_START (EEPROM_RESERV_CPU_LOW + EEPROM_RESERV_BOARD_LOW) +#define REG_MAGIC_LOC (REG_START) +#define REG_END_PTR_LOC (REG_MAGIC_LOC + MAGIC_SIZE) +#define REG_ENT1_LOC (REG_END_PTR_LOC + EEPREG_PTR_LEN) +#define DAT_START (EEPROM_SIZE - EEPROM_RESERV_CPU_HI \ + - EEPROM_RESERV_BOARD_HI - 1) + +static inline uint32_t _read_meta_uint(uint32_t loc) +{ + uint8_t data[4]; + uint32_t ret; + + eeprom_read(loc, data, EEPREG_PTR_LEN); + + /* unused array members will be discarded */ + ret = ((uint32_t)data[0] << 24) + | ((uint32_t)data[1] << 16) + | ((uint32_t)data[2] << 8) + | ((uint32_t)data[3]); + + /* bit shift to discard unused array members */ + ret >>= 8 * (4 - EEPREG_PTR_LEN); + + return ret; +} + +static inline void _write_meta_uint(uint32_t loc, uint32_t val) +{ + uint8_t data[4]; + + val <<= 8 * (4 - EEPREG_PTR_LEN); + + data[0] = (uint8_t)(val >> 24); + data[1] = (uint8_t)(val >> 16); + data[2] = (uint8_t)(val >> 8); + data[3] = (uint8_t)val; + + eeprom_write(loc, data, EEPREG_PTR_LEN); +} + +static inline uint32_t _get_reg_end(void) +{ + return _read_meta_uint(REG_END_PTR_LOC); +} + +static inline void _set_reg_end(uint32_t loc) +{ + _write_meta_uint(REG_END_PTR_LOC, loc); +} + +static inline uint32_t _get_last_loc(uint32_t reg_end) +{ + if (reg_end == REG_ENT1_LOC) { + /* no entries yet */ + return DAT_START; + } + + return _read_meta_uint(reg_end - EEPREG_PTR_LEN); +} + +static inline uint32_t _calc_free_space(uint32_t reg_end, uint32_t last_loc) +{ + return last_loc - reg_end; +} + +static inline uint8_t _get_meta_len(uint32_t meta_loc) +{ + return eeprom_read_byte(meta_loc); +} + +static inline void _set_meta_len(uint32_t meta_loc, uint8_t meta_len) +{ + eeprom_write_byte(meta_loc, meta_len); +} + +static inline uint32_t _get_data_loc(uint32_t meta_loc, uint8_t meta_len) +{ + /* data location is at the end of meta-data */ + return _read_meta_uint(meta_loc + meta_len - EEPREG_PTR_LEN); +} + +static inline void _set_data_loc(uint32_t meta_loc, uint8_t meta_len, + uint32_t data_loc) +{ + /* data location is at the end of meta-data */ + _write_meta_uint(meta_loc + meta_len - EEPREG_PTR_LEN, data_loc); +} + +static inline uint8_t _calc_name_len(uint8_t meta_len) +{ + /* entry contents: meta-data length, name, data pointer */ + return meta_len - ENT_LEN_SIZ - EEPREG_PTR_LEN; +} + +static inline void _get_name(uint32_t meta_loc, char *name, uint8_t meta_len) +{ + /* name is after entry length */ + eeprom_read(meta_loc + ENT_LEN_SIZ, (uint8_t *)name, + _calc_name_len(meta_len)); +} + +static inline int _cmp_name(uint32_t meta_loc, const char *name, + uint8_t meta_len) +{ + /* name is after entry length */ + uint32_t loc = meta_loc + ENT_LEN_SIZ; + + uint8_t len = _calc_name_len(meta_len); + + uint8_t offset; + for (offset = 0; offset < len; offset++) { + if (name[offset] == '\0') { + /* entry name is longer than name */ + return 0; + } + + if (eeprom_read_byte(loc + offset) != (uint8_t)name[offset]) { + /* non-matching character */ + return 0; + } + } + + if (name[offset] == '\0') { + /* entry name is the same length as name */ + return 1; + } + + /* entry name is shorter than name */ + return 0; +} + +static inline uint32_t _get_meta_loc(const char *name) +{ + uint32_t meta_loc = REG_ENT1_LOC; + uint32_t reg_end = _get_reg_end(); + + while (meta_loc < reg_end) { + uint8_t meta_len = _get_meta_len(meta_loc); + + if (_cmp_name(meta_loc, name, meta_len)) { + return meta_loc; + } + + meta_loc += meta_len; + } + + /* no meta-data found */ + return (uint32_t)UINT_MAX; +} + +static inline uint32_t _get_data_len(uint32_t meta_loc, uint32_t data_loc) +{ + uint32_t prev_loc; + if (meta_loc == REG_ENT1_LOC) { + prev_loc = DAT_START; + } + else { + /* previous entry data pointer is just before this entry */ + prev_loc = _read_meta_uint(meta_loc - EEPREG_PTR_LEN); + } + + return prev_loc - data_loc; +} + +static inline int _new_entry(const char *name, uint32_t data_len) +{ + uint32_t reg_end = _get_reg_end(); + uint32_t last_loc = _get_last_loc(reg_end); + uint32_t free_space = _calc_free_space(reg_end, last_loc); + + uint8_t name_len = (uint8_t)strlen(name); + uint8_t meta_len = ENT_LEN_SIZ + name_len + EEPREG_PTR_LEN; + + /* check to see if there is enough room */ + if (free_space < meta_len + data_len) { + return -ENOSPC; + } + + /* set the length of the meta-data */ + _set_meta_len(reg_end, meta_len); + + /* write name of entry */ + eeprom_write(reg_end + ENT_LEN_SIZ, (uint8_t *)name, name_len); + + /* set the location of the data */ + _set_data_loc(reg_end, meta_len, last_loc - data_len); + + /* update end of the registry */ + _set_reg_end(reg_end + meta_len); + + return 0; +} + +static inline void _move_data(uint32_t oldpos, uint32_t newpos, uint32_t len) +{ + for (uint32_t count = 0; count < len; count++) { + uint32_t offset; + + if (newpos < oldpos) { + /* move from beginning of data */ + offset = count; + } + else { + /* move from end of data */ + offset = len - count; + } + + uint8_t byte = eeprom_read_byte(oldpos + offset); + + eeprom_write_byte(newpos + offset, byte); + } +} + +int eepreg_add(uint32_t *pos, const char *name, uint32_t len) +{ + int ret = eepreg_check(); + if (ret == -ENOENT) { + /* reg does not exist, so make a new one */ + eepreg_reset(); + } + else if (ret < 0) { + DEBUG("[eepreg_add] eepreg_check failed\n"); + return ret; + } + + uint32_t reg_end = _get_reg_end(); + + uint32_t meta_loc = _get_meta_loc(name); + + if (meta_loc == (uint32_t)UINT_MAX) { + /* entry does not exist, so make a new one */ + + /* location of the new data */ + *pos = _get_last_loc(reg_end) - len; + + if (_new_entry(name, len) < 0) { + DEBUG("[eepreg_add] not enough space for %s\n", name); + return -ENOSPC; + } + + return 0; + } + + *pos = _get_data_loc(meta_loc, _get_meta_len(meta_loc)); + + if (len != _get_data_len(meta_loc, *pos)) { + DEBUG("[eepreg_add] %s already exists with different length\n", name); + return -EADDRINUSE; + } + + return 0; +} + +int eepreg_read(uint32_t *pos, const char *name) +{ + int ret = eepreg_check(); + if (ret < 0) { + DEBUG("[eepreg_read] eepreg_check failed\n"); + return ret; + } + + uint32_t meta_loc = _get_meta_loc(name); + + if (meta_loc == (uint32_t)UINT_MAX) { + DEBUG("[eepreg_read] no entry for %s\n", name); + return -ENOENT; + } + + *pos = _get_data_loc(meta_loc, _get_meta_len(meta_loc)); + + return 0; +} + +int eepreg_write(uint32_t *pos, const char *name, uint32_t len) +{ + uint32_t reg_end = _get_reg_end(); + + int ret = eepreg_check(); + if (ret == -ENOENT) { + /* reg does not exist, so make a new one */ + eepreg_reset(); + } + else if (ret < 0) { + DEBUG("[eepreg_write] eepreg_check failed\n"); + return ret; + } + + /* location of the new data */ + *pos = _get_last_loc(reg_end) - len; + + if (_new_entry(name, len) < 0) { + DEBUG("[eepreg_write] not enough space for %s\n", name); + return -ENOSPC; + } + + return 0; +} + +int eepreg_rm(const char *name) +{ + int ret = eepreg_check(); + if (ret < 0) { + DEBUG("[eepreg_rm] eepreg_check failed\n"); + return ret; + } + + uint32_t meta_loc = _get_meta_loc(name); + + if (meta_loc == (uint32_t)UINT_MAX) { + DEBUG("[eepreg_rm] no entry for %s\n", name); + return -ENOENT; + } + + uint32_t reg_end = _get_reg_end(); + uint32_t last_loc = _get_last_loc(reg_end); + + uint8_t meta_len = _get_meta_len(meta_loc); + uint32_t tot_meta_len = reg_end - meta_loc; + + uint32_t data_loc = _get_data_loc(meta_loc, meta_len); + uint32_t data_len = _get_data_len(meta_loc, data_loc); + + /* data_loc is above last_loc due to descending order */ + uint32_t tot_data_len = data_loc - last_loc; + + _move_data(meta_loc + meta_len, meta_loc, tot_meta_len); + + _move_data(last_loc, last_loc + data_len, tot_data_len); + + reg_end -= meta_len; + _set_reg_end(reg_end); + + /* update data locations */ + while (meta_loc < reg_end) { + meta_len = _get_meta_len(meta_loc); + data_loc = _get_data_loc(meta_loc, meta_len); + + /* addition due to descending order */ + _set_data_loc(meta_loc, meta_len, data_loc + data_len); + + meta_loc += meta_len; + } + + return 0; +} + +int eepreg_iter(eepreg_iter_cb_t cb, void *arg) +{ + uint32_t reg_end = _get_reg_end(); + + int ret = eepreg_check(); + if (ret < 0) { + DEBUG("[eepreg_len] eepreg_check failed\n"); + return ret; + } + + uint32_t meta_loc = REG_ENT1_LOC; + while (meta_loc < reg_end) { + uint8_t meta_len = _get_meta_len(meta_loc); + + /* size of memory allocation */ + uint8_t name_len = _calc_name_len(meta_len); + + char name[name_len + 1]; + + /* terminate string */ + name[name_len] = '\0'; + + _get_name(meta_loc, name, meta_len); + + /* execute callback */ + ret = cb(name, arg); + + if (ret < 0) { + DEBUG("[eepreg_iter] callback reports failure\n"); + return ret; + } + + /* only advance if cb didn't delete entry */ + if (_cmp_name(meta_loc, name, meta_len)) { + meta_loc += meta_len; + } + } + + return 0; +} + +int eepreg_check(void) +{ + char magic[MAGIC_SIZE]; + + /* get magic number from EEPROM */ + if (eeprom_read(REG_MAGIC_LOC, (uint8_t *)magic, MAGIC_SIZE) + != MAGIC_SIZE) { + + DEBUG("[eepreg_check] EEPROM read error\n"); + return -EIO; + } + + /* check to see if magic number is the same */ + if (strncmp(magic, eepreg_magic, MAGIC_SIZE) != 0) { + DEBUG("[eepreg_check] No registry detected\n"); + return -ENOENT; + } + + return 0; +} + +int eepreg_reset(void) +{ + /* write new registry magic number */ + if (eeprom_write(REG_MAGIC_LOC, (uint8_t *)eepreg_magic, MAGIC_SIZE) + != MAGIC_SIZE) { + + DEBUG("[eepreg_reset] EEPROM write error\n"); + return -EIO; + } + + /* new registry has no entries */ + _set_reg_end(REG_ENT1_LOC); + + return 0; +} + +int eepreg_len(uint32_t *len, const char *name) +{ + int ret = eepreg_check(); + if (ret < 0) { + DEBUG("[eepreg_len] eepreg_check failed\n"); + return ret; + } + + uint32_t meta_loc = _get_meta_loc(name); + + if (meta_loc == (uint32_t)UINT_MAX) { + DEBUG("[eepreg_len] no entry for %s\n", name); + return -ENOENT; + } + + uint32_t data_loc = _get_data_loc(meta_loc, _get_meta_len(meta_loc)); + + *len = _get_data_len(meta_loc, data_loc); + + return 0; +} + +int eepreg_free(uint32_t *len) +{ + int ret = eepreg_check(); + if (ret < 0) { + DEBUG("[eepreg_free] eepreg_check failed\n"); + return ret; + } + + uint32_t reg_end = _get_reg_end(); + uint32_t last_loc = _get_last_loc(reg_end); + *len = _calc_free_space(reg_end, last_loc); + + return 0; +} diff --git a/sys/include/eepreg.h b/sys/include/eepreg.h new file mode 100644 index 0000000000000000000000000000000000000000..269d6c3044bdcc25ea64d603a3f423c748ded224 --- /dev/null +++ b/sys/include/eepreg.h @@ -0,0 +1,271 @@ +/* + * Copyright (C) 2018 Acutam Automation, LLC + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @defgroup sys_eepreg EEPROM registration + * @ingroup sys + * @brief eepreg provides a facility to easily manage the locations of + * data stored in EEPROM via a meta-data registry. + * + * The structure of the meta-data registry is intended to make it easy to + * detect the exact layout of existent data so that automatic tools may be + * written to migrate legacy data to new formats. It also allows the addition + * and removal of new entries dynamically. + * + * @note Names are used as identifiers and must be unique! It is also + * recommended to keep them as short as possible (while still being unique and + * human readable), as many systems have very small amounts of EEPROM. + * Disemvowelment can shorten long names while still retaining readability. + * + * @code {unparsed} + * The layout of the EEPROM used looks like this: + * EEPROM_RESERV_CPU_LOW + * EEPROM_RESERV_BOARD_LOW + * Registry magic number ("RIOTREG") + * Registry end pointer + * Registry entry 1 meta-data length (1 byte) + * Registry entry 1 name (unterminated) + * Registry entry 1 data pointer + * Registry entry 2 meta-data length + * Registry entry 2 name + * Registry entry 2 data pointer + * ... (new registry meta-data may be added in ascending order) + * unused space + * ... (new data locations may be added in descending order) + * Entry 2 data + * Entry 1 data + * EEPROM_RESERV_BOARD_HI + * EEPROM_RESERV_CPU_HI + * @endcode + * + * Pointer length is dependent on the size of the available EEPROM (see + * EEPREG_PTR_LEN below). + * + * @{ + * + * @file + * @brief eepreg interface definitions + * + * @author Matthew Blue <matthew.blue.neuro@gmail.com> + */ + +#ifndef EEPREG_H +#define EEPREG_H + +#include <stdint.h> + +#include "periph_cpu.h" +#include "periph_conf.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef EEPROM_RESERV_CPU_LOW +/** + * @brief EEPROM reserved near beginning for use by CPU and related + * + * Change with care, as it may make existing data difficult to migrate + */ +#define EEPROM_RESERV_CPU_LOW (0U) +#endif + +#ifndef EEPROM_RESERV_CPU_HI +/** + * @brief EEPROM reserved near end for use by CPU and related + * + * Change with care, as it may make existing data difficult to migrate + */ +#define EEPROM_RESERV_CPU_HI (0U) +#endif + +#ifndef EEPROM_RESERV_BOARD_LOW +/** + * @brief EEPROM reserved near beginning for use by board and related + * + * Change with care, as it may make existing data difficult to migrate + */ +#define EEPROM_RESERV_BOARD_LOW (0U) +#endif + +#ifndef EEPROM_RESERV_BOARD_HI +/** + * @brief EEPROM reserved near end for use by board and related + * + * Change with care, as it may make existing data difficult to migrate + */ +#define EEPROM_RESERV_BOARD_HI (0U) +#endif + +/** + * @brief Size in bytes of pointer meta-data in EEPROM + */ +#if (EEPROM_SIZE > 0x1000000) +#define EEPREG_PTR_LEN (4U) +#elif (EEPROM_SIZE > 0x10000) +#define EEPREG_PTR_LEN (3U) +#elif (EEPROM_SIZE > 0x100) +#define EEPREG_PTR_LEN (2U) +#else +#define EEPREG_PTR_LEN (1U) +#endif + +/** + * @brief Signature of callback for iterating over entries in EEPROM registry + * + * @param[in] name name of an entry in the registry + * @param[in] arg argument for cb + * + * @return 0 on success + * @return < 0 on failure + */ +typedef int (*eepreg_iter_cb_t)(char *name, void *arg); + +/** + * @brief Load or write meta-data in EEPROM registry + * + * This checks to see if relevant meta-data exists in the EEPROM registry, and + * returns that data position if it exists. If an entry does not exist in the + * registry, meta-data is written and allocated data space if there is enough + * remaining. Requesting a different length for an existent entry returns an + * error. + * + * @param[out] pos pointer to position variable + * @param[in] name name of entry to load or write + * @param[in] len requested amount of data storage + * + * @return 0 on success + * @return -EIO on EEPROM I/O error + * @return -ENOSPC on insufficient EEPROM for entry + * @return -EADDRINUSE on existing entry with different length + */ +int eepreg_add(uint32_t *pos, const char *name, uint32_t len); + +/** + * @brief Read position meta-data from EEPROM registry + * + * This is similar to eepreg_add, except it never writes meta-data. + * + * @param[out] pos pointer to position variable + * @param[in] name name of entry to load + * + * @return 0 on success + * @return -EIO on EEPROM I/O error + * @return -ENOENT on non-existent registry or entry + */ +int eepreg_read(uint32_t *pos, const char *name); + +/** + * @brief Write meta-data to EEPROM registry + * + * This ignores existing meta-data and always makes a new entry in the + * registry. Typical use should be through eepreg_add and not eepreg_write. + * If multiple entries with the same name exist, eepreg functions will find + * the oldest. Mainly intended for use by migration utilities. + * + * @param[out] pos pointer to position variable + * @param[in] name name of entry to write + * @param[in] len requested amount of data storage + * + * @return 0 on success + * @return -EIO on EEPROM I/O error + * @return -ENOSPC on insufficient EEPROM for entry + */ +int eepreg_write(uint32_t *pos, const char *name, uint32_t len); + +/** + * @brief Remove entry from EEPROM registry and free space + * + * This removes an entry from the EEPROM registry and its corresponding data + * and moves the data and meta-data of entries after removed entry to occupy + * the freed space. This preserves the structure of the EEPROM registry. + * Warning: this is a read/write intensive operation! Mainly intended for use + * by migration utilities. + * + * @param[in] name name of entry to remove + * + * @return 0 on success + * @return -EIO on EEPROM I/O error + * @return -ENOENT on non-existent registry or entry + */ +int eepreg_rm(const char *name); + +/** + * @brief Iterate over meta-data entries in EEPROM registry + * + * This executes a callback over each name in the EEPROM registry. The intended + * work-flow for migration is to: iterate over each entry, check to see if + * migration is needed, duplicate using eepreg_write if needed, migrate data to + * duplicate entry, then delete old entry using eepreg_rm. + * + * @note It is safe for the callback to remove the entry it is called with, + * or to add new entries. + * + * @param[in] cb callback to iterate over entries + * @param[in] arg argument for cb + * + * @return 0 on success + * @return -EIO on EEPROM I/O error + * @return -ENOENT on non-existent registry + * @return return value of cb when cb returns < 0 + */ +int eepreg_iter(eepreg_iter_cb_t cb, void *arg); + +/** + * @brief Check for the presence of meta-data registry + * + * @return 0 on success + * @return -EIO on EEPROM I/O error + * @return -ENOENT on non-existent registry + */ +int eepreg_check(void); + +/** + * @brief Clear existing meta-data registry + * + * This removes any existing meta-data registry by writing a new registry with + * no entries. + * + * @return 0 on success + * @return -EIO on EEPROM I/O error + */ +int eepreg_reset(void); + +/** + * @brief Calculate data length from meta-data in EEPROM registry + * + * @note This information is typically already available to code that has + * called eepreg_add. + * + * @param[out] len pointer to length variable + * @param[in] name name of entry to load or write + * + * @return 0 on success + * @return -EIO on EEPROM I/O error + * @return -ENOENT on non-existent registry or entry + */ +int eepreg_len(uint32_t *len, const char *name); + +/** + * @brief Calculate length of remaining EEPROM free space + * + * @param[out] len pointer to length variable + * + * @return 0 on success + * @return -EIO on EEPROM I/O error + * @return -ENOENT on non-existent registry + */ +int eepreg_free(uint32_t *len); + +#ifdef __cplusplus +} +#endif + +/** @} */ + +#endif /* EEPREG_H */