409 lines
10 KiB
C
Executable File
409 lines
10 KiB
C
Executable File
#include <string.h>
|
|
#include <di/config.h>
|
|
#include <di/log.h>
|
|
#include <di/crc.h>
|
|
|
|
#define DI_CONFIG_FILL_BYTE 0x00 /**< Configuration fill byte pattern */
|
|
#define DI_CONFIG_CRC_CCIT16_OFFSET 2U /**< CRC offset/size written before application data */
|
|
|
|
/**
|
|
* Search for a configuration item by unique id
|
|
* @param ctx Configuration context
|
|
* @param uid Unique ID
|
|
*/
|
|
static struct di_config_item *di_config_uid_search(struct di_config_ctx *ctx, unsigned int uid)
|
|
{
|
|
di_array_foreach(ctx->items, it, struct di_config_item) {
|
|
if (it->uid == uid)
|
|
return it;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* Wrapper for di_config_get and ctx->read
|
|
* It first will read the CRC-CCIT16 and then read the data.
|
|
* Then the crc and returned read size checked
|
|
*
|
|
* @param ctx Configuration context
|
|
* @param offset Memory offset in bytes to read from
|
|
* @param data Address of buffer to read into
|
|
* @param size Size to read
|
|
*
|
|
* @retval DNE_IOFAILED When there was a error reading, the amount of requested bytes read doesn't match
|
|
* @retval DNE_CHECKSUM Invalid checksum after read
|
|
* @retval DNOK When read was succesfull and requested bytes are successfully read and checksummed
|
|
*/
|
|
static di_errno_t di_config_read(struct di_config_ctx *ctx, uint32_t offset, void *data, size_t size)
|
|
{
|
|
uint16_t crc;
|
|
di_errno_t ret = DNOK;
|
|
|
|
// read checksum
|
|
ret = ctx->read(ctx, offset, &crc, sizeof(crc));
|
|
if (ret != DNOK)
|
|
return ret;
|
|
|
|
// read data
|
|
ret = ctx->read(ctx, offset + DI_CONFIG_CRC_CCIT16_OFFSET, data, size);
|
|
if (ret != DNOK)
|
|
return ret;
|
|
|
|
if (crc != di_crc16_ccitt(data, size))
|
|
ret = DNE_CHECKSUM;
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* Calculate the crc of data and optional zero fill
|
|
* @param data Application data
|
|
* @param size Size of application data
|
|
* @param fill Amount of bytes to fill with zero after application data
|
|
*/
|
|
static uint16_t di_config_calc_crc(const void *data, size_t size, size_t fill)
|
|
{
|
|
uint8_t fill_byte = DI_CONFIG_FILL_BYTE;
|
|
uint16_t crc;
|
|
|
|
crc = di_crc16_ccitt(data, size);
|
|
if (fill > 0) {
|
|
for (size_t n = 0; n < fill; n++)
|
|
di_crc16_ccitt_update(&crc, &fill_byte, 1);
|
|
}
|
|
|
|
return crc;
|
|
}
|
|
|
|
/**
|
|
* Write fill byte(s)
|
|
* @param ctx Configuration context
|
|
* @param offset Write offset start
|
|
* @param size Amount of fill bytes to write
|
|
*/
|
|
static di_errno_t di_config_write_fill(struct di_config_ctx *ctx, uint32_t offset, size_t size)
|
|
{
|
|
size_t n;
|
|
uint8_t fill_byte = DI_CONFIG_FILL_BYTE;
|
|
di_errno_t ret = DNOK;
|
|
|
|
for (n = 0; n < size; n++) {
|
|
if (ret != DNOK)
|
|
break;
|
|
|
|
ret = ctx->write(ctx, offset, &fill_byte, sizeof(fill_byte));
|
|
offset += 1;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* Wrapper for di_config_set and ctx->write
|
|
* It first will write the CRC-CCIT16 and then write the data.
|
|
* When writes are successful it runs the low-level sync callback.
|
|
*
|
|
* @param ctx Configuration context
|
|
* @param uid Item unique id
|
|
* @param offset Memory offset in bytes to write into
|
|
* @param data Address of buffer to write data from
|
|
* @param size Size of data buffer
|
|
*
|
|
* @retval DNE_FIRMWARE_EEPROM When there was an error writing
|
|
* @retval DNOK When write was succesfull and requested bytes are successfully written
|
|
*/
|
|
static di_errno_t di_config_write(struct di_config_ctx *ctx, unsigned int uid, uint32_t offset,
|
|
const void *data, size_t size)
|
|
{
|
|
struct di_config_item *item;
|
|
size_t fill = 0;
|
|
uint16_t crc;
|
|
di_errno_t ret = DNOK;
|
|
|
|
/* Fetch item from uid to get the maximum size in bytes, calculate remaining fill */
|
|
item = di_config_uid_search(ctx, uid);
|
|
if (size < item->size)
|
|
fill = item->size - size;
|
|
|
|
crc = di_config_calc_crc(data, size, fill);
|
|
ret = ctx->write(ctx, offset, &crc, sizeof(crc));
|
|
if (ret != DNOK)
|
|
return ret;
|
|
|
|
/* Write application config item data */
|
|
ret = ctx->write(ctx, offset + DI_CONFIG_CRC_CCIT16_OFFSET, data, size);
|
|
if (ret != DNOK)
|
|
return ret;
|
|
|
|
/* Fill remainder after application data size */
|
|
if (fill)
|
|
ret = di_config_write_fill(ctx, offset + DI_CONFIG_CRC_CCIT16_OFFSET + (uint32_t)size, fill);
|
|
|
|
if (ctx->sync)
|
|
ctx->sync(ctx);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* Check requested size is less than or equal to the assigned id size.
|
|
* The requester my read or write less or equal the amount of bytes defined by the id.
|
|
* This because strings and blobs can have dynamic sizes.
|
|
*
|
|
* @param ctx The config context
|
|
* @param uid The item unique id
|
|
* @param size The size of the operation (read/write)
|
|
*
|
|
* @retval DNE_NOTFOUND Item unique id not found
|
|
* @retval DNE_RANGE Size is out of range
|
|
* @retval DNOK Size is valid
|
|
*/
|
|
static di_errno_t di_config_uid_check_size(struct di_config_ctx *ctx, unsigned int uid, size_t size)
|
|
{
|
|
struct di_config_item *_uid;
|
|
|
|
_uid = di_config_uid_search(ctx, uid);
|
|
if (!_uid)
|
|
return DNE_NOTFOUND;
|
|
|
|
if (size <= _uid->size)
|
|
return DNOK;
|
|
|
|
return DNE_RANGE;
|
|
}
|
|
|
|
/**
|
|
* Calculate data offset of item unique id, the offset is the first byte pointing at the CRC
|
|
*
|
|
* @param ctx Config context
|
|
* @param uid Item unique id
|
|
* @param[out] offset Offset when id is found
|
|
*
|
|
* @retval DNE_NOTFOUND when id is not known
|
|
* @retval DNOK when id is known and offset is calculated
|
|
*/
|
|
static di_errno_t di_config_uid_get_offset(struct di_config_ctx *ctx, unsigned int uid, uint32_t *offset)
|
|
{
|
|
di_errno_t ret = DNE_NOTFOUND;
|
|
|
|
*offset = 0;
|
|
|
|
di_array_foreach(ctx->items, it, struct di_config_item) {
|
|
if (it->uid == uid) {
|
|
ret = DNOK;
|
|
break;
|
|
}
|
|
*offset += (uint32_t)(DI_CONFIG_CRC_CCIT16_OFFSET + it->size);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
void di_config_init(struct di_config_ctx *ctx)
|
|
{
|
|
ctx->read = NULL;
|
|
ctx->write = NULL;
|
|
ctx->sync = NULL;
|
|
|
|
ctx->items.begin = NULL;
|
|
ctx->items.size = 0;
|
|
|
|
di_bsem_init_unlocked(&ctx->lock);
|
|
}
|
|
|
|
di_errno_t di_config_set_items(struct di_config_ctx *ctx, const struct di_config_item *items, size_t size)
|
|
{
|
|
di_errno_t ret = DNOK;
|
|
unsigned int duplicates = 0;
|
|
|
|
ctx->items.begin = (void *)items;
|
|
ctx->items.size = size;
|
|
|
|
/* Search for duplicate items */
|
|
di_array_foreach(ctx->items, itx, struct di_config_item) {
|
|
duplicates = 0;
|
|
|
|
di_array_foreach(ctx->items, ity, struct di_config_item) {
|
|
if (itx->uid == ity->uid)
|
|
duplicates++;
|
|
}
|
|
|
|
/* One duplicate is the item itself */
|
|
if (duplicates > 1) {
|
|
ctx->items.begin = NULL;
|
|
ctx->items.size = 0;
|
|
|
|
ret = DNE_PARAM;
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
di_errno_t di_config_set_default(struct di_config_ctx *ctx, unsigned int uid)
|
|
{
|
|
const uint8_t *data;
|
|
size_t size;
|
|
struct di_config_item *item;
|
|
|
|
item = di_config_uid_search(ctx, uid);
|
|
if (!item)
|
|
return DNE_NOTFOUND;
|
|
|
|
switch (item->type) {
|
|
case DI_CONFIG_ITEM_DEFAULT_TYPE_BOOL:
|
|
data = (const uint8_t *)&item->def.b;
|
|
size = sizeof(item->def.b);
|
|
break;
|
|
case DI_CONFIG_ITEM_DEFAULT_TYPE_INT8:
|
|
data = (const uint8_t *)&item->def.s8;
|
|
size = sizeof(item->def.s8);
|
|
break;
|
|
case DI_CONFIG_ITEM_DEFAULT_TYPE_INT16:
|
|
data = (const uint8_t *)&item->def.s16;
|
|
size = sizeof(item->def.s16);
|
|
break;
|
|
case DI_CONFIG_ITEM_DEFAULT_TYPE_INT32:
|
|
data = (const uint8_t *)&item->def.s32;
|
|
size = sizeof(item->def.s32);
|
|
break;
|
|
case DI_CONFIG_ITEM_DEFAULT_TYPE_INT64:
|
|
data = (const uint8_t *)&item->def.s64;
|
|
size = sizeof(item->def.s64);
|
|
break;
|
|
case DI_CONFIG_ITEM_DEFAULT_TYPE_UINT8:
|
|
data = (const uint8_t *)&item->def.u8;
|
|
size = sizeof(item->def.u8);
|
|
break;
|
|
case DI_CONFIG_ITEM_DEFAULT_TYPE_UINT16:
|
|
data = (const uint8_t *)&item->def.u16;
|
|
size = sizeof(item->def.u16);
|
|
break;
|
|
case DI_CONFIG_ITEM_DEFAULT_TYPE_UINT32:
|
|
data = (const uint8_t *)&item->def.u32;
|
|
size = sizeof(item->def.u32);
|
|
break;
|
|
case DI_CONFIG_ITEM_DEFAULT_TYPE_UINT64:
|
|
data = (const uint8_t *)&item->def.u64;
|
|
size = sizeof(item->def.u64);
|
|
break;
|
|
case DI_CONFIG_ITEM_DEFAULT_TYPE_FLOAT:
|
|
data = (const uint8_t *)&item->def.f;
|
|
size = sizeof(item->def.f);
|
|
break;
|
|
case DI_CONFIG_ITEM_DEFAULT_TYPE_DOUBLE:
|
|
data = (const uint8_t *)&item->def.d;
|
|
size = sizeof(item->def.d);
|
|
break;
|
|
case DI_CONFIG_ITEM_DEFAULT_TYPE_STR:
|
|
data = (const uint8_t *)item->def.str;
|
|
size = 0;
|
|
break;
|
|
default:
|
|
return DNE_PARAM;
|
|
}
|
|
|
|
return _di_config_set(ctx, uid, data, size);
|
|
}
|
|
|
|
di_errno_t _di_config_set(struct di_config_ctx *ctx, unsigned int uid, const uint8_t *data, size_t size)
|
|
{
|
|
di_errno_t ret;
|
|
uint32_t offset;
|
|
|
|
if (!ctx->write || !ctx->items.begin)
|
|
return DNE_OPNOTSUPP;
|
|
|
|
/* We calculate the string size, and add space for a null terminator (with fill) */
|
|
if (size == 0)
|
|
size = strlen((const char *)data) + 1;
|
|
|
|
ret = di_config_uid_check_size(ctx, uid, size);
|
|
if (ret != DNOK)
|
|
return ret;
|
|
|
|
ret = di_config_uid_get_offset(ctx, uid, &offset);
|
|
if (ret != DNOK)
|
|
return ret;
|
|
|
|
/* Check if set operation is out-of-bound */
|
|
if ((offset + size) >= ctx->size)
|
|
return DNE_RANGE;
|
|
|
|
di_bsem_wait(&ctx->lock);
|
|
|
|
ret = di_config_write(ctx, uid, offset, data, size);
|
|
|
|
di_bsem_post(&ctx->lock);
|
|
|
|
return ret;
|
|
}
|
|
|
|
di_errno_t _di_config_get(struct di_config_ctx *ctx, unsigned int uid, uint8_t *data, size_t size)
|
|
{
|
|
di_errno_t ret;
|
|
uint32_t offset;
|
|
|
|
if (!ctx->read || !ctx->items.begin)
|
|
return DNE_OPNOTSUPP;
|
|
|
|
ret = di_config_uid_check_size(ctx, uid, size);
|
|
if (ret != DNOK)
|
|
return ret;
|
|
|
|
ret = di_config_uid_get_offset(ctx, uid, &offset);
|
|
if (ret != DNOK)
|
|
return ret;
|
|
|
|
/* Check if get operation is out-of-bound */
|
|
if ((offset + size) >= ctx->size)
|
|
return DNE_RANGE;
|
|
|
|
di_bsem_wait(&ctx->lock);
|
|
|
|
ret = di_config_read(ctx, offset, data, size);
|
|
|
|
di_bsem_post(&ctx->lock);
|
|
|
|
return ret;
|
|
}
|
|
|
|
di_errno_t _di_config_get_or_load_default(struct di_config_ctx *ctx, unsigned int uid,
|
|
uint8_t *data, size_t size)
|
|
{
|
|
di_errno_t ret = DNE_IOFAILED;
|
|
|
|
ret = _di_config_get(ctx, uid, data, size);
|
|
if (ret == DNOK)
|
|
return ret;
|
|
|
|
ret = di_config_set_default(ctx, uid);
|
|
if (ret != DNOK)
|
|
return ret;
|
|
|
|
ret = _di_config_get(ctx, uid, data, size);
|
|
|
|
return ret;
|
|
}
|
|
|
|
di_errno_t di_config_get_or_load_default_string(struct di_config_ctx *ctx, unsigned int uid,
|
|
char *str, size_t size)
|
|
{
|
|
di_errno_t ret = DNE_IOFAILED;
|
|
|
|
ret = di_config_get_string(ctx, uid, str, size);
|
|
if (ret == DNOK)
|
|
return ret;
|
|
|
|
ret = di_config_set_default(ctx, uid);
|
|
if (ret != DNOK)
|
|
return ret;
|
|
|
|
ret = _di_config_get(ctx, uid, (uint8_t *)str, size);
|
|
|
|
return ret;
|
|
}
|