bindesc: Add support for reading binary descriptors

This commit adds support for reading and parsing binary descriptors.
It can be used for reading the descriptors of another image, or for
iterating over one's own descriptors.

Signed-off-by: Yonatan Schachter <yonatan.schachter@gmail.com>
This commit is contained in:
Yonatan Schachter 2023-12-20 19:06:14 +02:00 committed by Anas Nashif
parent fd68fc486c
commit 00800d4818
4 changed files with 461 additions and 5 deletions

View file

@ -23,6 +23,8 @@ extern "C" {
#define BINDESC_TYPE_STR 0x1
#define BINDESC_TYPE_BYTES 0x2
#define BINDESC_TYPE_DESCRIPTORS_END 0xf
/* sizeof ignores the data as it's a flexible array */
#define BINDESC_ENTRY_HEADER_SIZE (sizeof(struct bindesc_entry))
/**
* @brief Binary Descriptor Definition
@ -131,11 +133,18 @@ extern "C" {
*/
#define BINDESC_TAG(type, id) ((BINDESC_TYPE_##type & 0xf) << 12 | (id & 0x0fff))
/**
* @brief Utility macro to get the type of a bindesc tag
*
* @param tag Tag to get the type of
*/
#define BINDESC_GET_TAG_TYPE(tag) ((tag >> 12) & 0xf)
/**
* @endcond
*/
#if !defined(_LINKER)
#if !defined(_LINKER) || defined(__DOXYGEN__)
#include <zephyr/sys/byteorder.h>
@ -278,6 +287,10 @@ extern "C" {
*/
#define BINDESC_GET_SIZE(name) BINDESC_NAME(name).len
/**
* @}
*/
/*
* An entry of the binary descriptor header. Each descriptor is
* described by one of these entries.
@ -301,6 +314,176 @@ BUILD_ASSERT(offsetof(struct bindesc_entry, tag) == 0, "Incorrect memory layout"
BUILD_ASSERT(offsetof(struct bindesc_entry, len) == 2, "Incorrect memory layout");
BUILD_ASSERT(offsetof(struct bindesc_entry, data) == 4, "Incorrect memory layout");
struct bindesc_handle {
const uint8_t *address;
enum {
BINDESC_HANDLE_TYPE_RAM,
BINDESC_HANDLE_TYPE_MEMORY_MAPPED_FLASH,
BINDESC_HANDLE_TYPE_FLASH,
} type;
size_t size_limit;
#if IS_ENABLED(CONFIG_BINDESC_READ_FLASH)
const struct device *flash_device;
uint8_t buffer[sizeof(struct bindesc_entry) +
CONFIG_BINDESC_READ_FLASH_MAX_DATA_SIZE] __aligned(BINDESC_ALIGNMENT);
#endif /* IS_ENABLED(CONFIG_BINDESC_READ_FLASH) */
};
/**
* @brief Reading Binary Descriptors of other images.
* @defgroup bindesc_read Bindesc Read
* @ingroup os_services
* @{
*/
/**
* @brief Callback type to be called on descriptors found during a walk
*
* @param entry Current descriptor
* @param user_data The user_data given to @ref bindesc_foreach
*
* @return Any non zero value will halt the walk
*/
typedef int (*bindesc_callback_t)(const struct bindesc_entry *entry, void *user_data);
/**
* @brief Open an image's binary descriptors for reading, from a memory mapped flash
*
* @details
* Initializes a bindesc handle for subsequent calls to bindesc API.
* Memory mapped flash is any flash that can be directly accessed by the CPU,
* without needing to use the flash API for copying the data to RAM.
*
* @param handle Bindesc handle to be given to subsequent calls
* @param offset The offset from the beginning of the flash that the bindesc magic can be found at
*
* @retval 0 On success
* @retval -ENOENT If no bindesc magic was found at the given offset
*/
int bindesc_open_memory_mapped_flash(struct bindesc_handle *handle, size_t offset);
/**
* @brief Open an image's binary descriptors for reading, from RAM
*
* @details
* Initializes a bindesc handle for subsequent calls to bindesc API.
* It's assumed that the whole bindesc context was copied to RAM prior to calling
* this function, either by the user or by a bootloader.
*
* @note The given address must be aligned to BINDESC_ALIGNMENT
*
* @param handle Bindesc handle to be given to subsequent calls
* @param address The address that the bindesc magic can be found at
* @param max_size Maximum size of the given buffer
*
* @retval 0 On success
* @retval -ENOENT If no bindesc magic was found at the given address
* @retval -EINVAL If the given address is not aligned
*/
int bindesc_open_ram(struct bindesc_handle *handle, const uint8_t *address, size_t max_size);
/**
* @brief Open an image's binary descriptors for reading, from flash
*
* @details
* Initializes a bindesc handle for subsequent calls to bindesc API.
* As opposed to reading bindesc from RAM or memory mapped flash, this
* backend requires reading the data from flash to an internal buffer
* using the flash API
*
* @param handle Bindesc handle to be given to subsequent calls
* @param offset The offset from the beginning of the flash that the bindesc magic can be found at
* @param flash_device Flash device to read descriptors from
*
* @retval 0 On success
* @retval -ENOENT If no bindesc magic was found at the given offset
*/
int bindesc_open_flash(struct bindesc_handle *handle, size_t offset,
const struct device *flash_device);
/**
* @brief Walk the binary descriptors and run a user defined callback on each of them
*
* @note
* If the callback returns a non zero value, the walk stops.
*
* @param handle An initialized bindesc handle
* @param callback A user defined callback to be called on each descriptor
* @param user_data User defined data to be given to the callback
*
* @return If the walk was finished prematurely by the callback,
* return the callback's retval, zero otherwise
*/
int bindesc_foreach(struct bindesc_handle *handle, bindesc_callback_t callback, void *user_data);
/**
* @brief Find a specific descriptor of type string
*
* @warning
* When using the flash backend, result will be invalidated by the next call to any bindesc API.
* Use the value immediately or copy it elsewhere.
*
* @param handle An initialized bindesc handle
* @param id ID to search for
* @param result Pointer to the found string
*
* @retval 0 If the descriptor was found
* @retval -ENOENT If the descriptor was not found
*/
int bindesc_find_str(struct bindesc_handle *handle, uint16_t id, const char **result);
/**
* @brief Find a specific descriptor of type uint
*
* @warning
* When using the flash backend, result will be invalidated by the next call to any bindesc API.
* Use the value immediately or copy it elsewhere.
*
* @param handle An initialized bindesc handle
* @param id ID to search for
* @param result Pointer to the found uint
*
* @retval 0 If the descriptor was found
* @retval -ENOENT If the descriptor was not found
*/
int bindesc_find_uint(struct bindesc_handle *handle, uint16_t id, const uint32_t **result);
/**
* @brief Find a specific descriptor of type bytes
*
* @warning
* When using the flash backend, result will be invalidated by the next call to any bindesc API.
* Use the value immediately or copy it elsewhere.
*
* @param handle An initialized bindesc handle
* @param id ID to search for
* @param result Pointer to the found bytes
* @param result_size Size of the found bytes
*
* @retval 0 If the descriptor was found
* @retval -ENOENT If the descriptor was not found
*/
int bindesc_find_bytes(struct bindesc_handle *handle, uint16_t id, const uint8_t **result,
size_t *result_size);
/**
* @brief Get the size of an image's binary descriptors
*
* @details
* Walks the binary descriptor structure to caluculate the total size of the structure
* in bytes. This is useful, for instance, if the whole structure is to be copied to RAM.
*
* @param handle An initialized bindesc handle
* @param result Pointer to write result to
*
* @return 0 On success, negative errno otherwise
*/
int bindesc_get_size(struct bindesc_handle *handle, size_t *result);
/**
* @}
*/
#if defined(CONFIG_BINDESC_KERNEL_VERSION_STRING)
extern const struct bindesc_entry BINDESC_NAME(kernel_version_string);
#endif /* defined(CONFIG_BINDESC_KERNEL_VERSION_STRING) */
@ -411,10 +594,6 @@ extern const struct bindesc_entry BINDESC_NAME(cxx_compiler_version);
#endif /* !defined(_LINKER) */
/**
* @}
*/
#ifdef __cplusplus
}
#endif

View file

@ -66,3 +66,5 @@ if(CONFIG_BINDESC_DEFINE_HOST_INFO)
gen_str_definition(CXX_COMPILER_NAME ${CMAKE_CXX_COMPILER_ID})
gen_str_definition(CXX_COMPILER_VERSION ${CMAKE_CXX_COMPILER_VERSION})
endif()
zephyr_library_sources_ifdef(CONFIG_BINDESC_READ bindesc_read.c)

View file

@ -31,4 +31,46 @@ config BINDESC_DEFINE_MAX_DATA_SIZE
endif # BINDESC_DEFINE
config BINDESC_READ
bool "Binary Descriptors Read"
help
Enable the app to read the binary descriptors of another image
if BINDESC_READ
module = BINDESC_READ
module-str = Binary Descriptor read
source "subsys/logging/Kconfig.template.log_config"
config BINDESC_READ_RAM
bool "Bindesc read from RAM"
help
Enable reading and parsing binary descriptors from RAM.
config BINDESC_READ_MEMORY_MAPPED_FLASH
bool "Bindesc read from memory mapped flash"
help
Enable reading and parsing binary descriptors from memory mapped flash.
config BINDESC_READ_FLASH
bool "Bindesc read from flash"
help
Enable reading and parsing binary descriptors from non memory mapped flash
(e.g. external flash).
if BINDESC_READ_FLASH
config BINDESC_READ_FLASH_MAX_DATA_SIZE
int "Bindesc read flash max data size"
range 4 $(UINT16_MAX)
default 128
help
The maximum expected size of the descriptors' data. This should be set to
the value set to BINDESC_DEFINE_MAX_DATA_SIZE by the read image.
Any descriptor that exceeds this size will be ignored.
endif # BINDESC_READ_FLASH
endif # BINDESC_READ
endif # BINDESC

View file

@ -0,0 +1,233 @@
/*
* Copyright (c) 2023 Yonatan Schachter
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/bindesc.h>
#include <zephyr/sys/util.h>
#include <zephyr/logging/log.h>
#include <zephyr/drivers/flash.h>
LOG_MODULE_REGISTER(bindesc_read, CONFIG_BINDESC_READ_LOG_LEVEL);
struct find_user_data {
const void *result;
size_t size;
uint16_t tag;
};
/**
* A callback used by the bindesc_find_* functions.
*/
static int find_callback(const struct bindesc_entry *entry, void *user_data)
{
struct find_user_data *data = (struct find_user_data *)user_data;
if (data->tag == entry->tag) {
data->result = (const void *)&(entry->data);
data->size = entry->len;
return 1;
}
return 0;
}
/**
* A callback used by the bindesc_get_size function.
*/
static int get_size_callback(const struct bindesc_entry *entry, void *user_data)
{
size_t *result = (size_t *)user_data;
*result += WB_UP(BINDESC_ENTRY_HEADER_SIZE + entry->len);
return 0;
}
/**
* This helper function is used to abstract the different methods of reading
* data from the binary descriptors.
* For RAM and memory mapped flash, the implementation is very simple, as both
* are memory mapped and can simply return a pointer to the data.
* Flash is more complex because it needs to read the data from flash, and do
* error checking.
*/
static inline int get_entry(struct bindesc_handle *handle, const uint8_t *address,
const struct bindesc_entry **entry)
{
int retval = 0;
int flash_retval;
/* Check if reading from flash is enabled, if not, this if/else will be optimized out */
if (IS_ENABLED(CONFIG_BINDESC_READ_FLASH) && handle->type == BINDESC_HANDLE_TYPE_FLASH) {
flash_retval = flash_read(handle->flash_device, (size_t)address,
handle->buffer, BINDESC_ENTRY_HEADER_SIZE);
if (flash_retval) {
LOG_ERR("Flash read error: %d", flash_retval);
return -EIO;
}
/* Make sure buffer is large enough for the data */
if (((const struct bindesc_entry *)handle->buffer)->len + BINDESC_ENTRY_HEADER_SIZE
> sizeof(handle->buffer)) {
LOG_WRN("Descriptor too large to copy, skipping");
retval = -ENOMEM;
} else {
flash_retval = flash_read(handle->flash_device,
(size_t)address + BINDESC_ENTRY_HEADER_SIZE,
handle->buffer + BINDESC_ENTRY_HEADER_SIZE,
((const struct bindesc_entry *)handle->buffer)->len);
if (flash_retval) {
LOG_ERR("Flash read error: %d", flash_retval);
return -EIO;
}
}
*entry = (const struct bindesc_entry *)handle->buffer;
} else {
*entry = (const struct bindesc_entry *)address;
}
return retval;
}
#if IS_ENABLED(CONFIG_BINDESC_READ_MEMORY_MAPPED_FLASH)
int bindesc_open_memory_mapped_flash(struct bindesc_handle *handle, size_t offset)
{
uint8_t *address = (uint8_t *)CONFIG_FLASH_BASE_ADDRESS + offset;
if (*(uint64_t *)address != BINDESC_MAGIC) {
LOG_ERR("Magic not found in given address");
return -ENOENT;
}
handle->address = address;
handle->type = BINDESC_HANDLE_TYPE_MEMORY_MAPPED_FLASH;
handle->size_limit = UINT16_MAX;
return 0;
}
#endif /* IS_ENABLED(CONFIG_BINDESC_READ_RAM) */
#if IS_ENABLED(CONFIG_BINDESC_READ_RAM)
int bindesc_open_ram(struct bindesc_handle *handle, const uint8_t *address, size_t max_size)
{
if (!IS_ALIGNED(address, BINDESC_ALIGNMENT)) {
LOG_ERR("Given address is not aligned");
return -EINVAL;
}
if (*(uint64_t *)address != BINDESC_MAGIC) {
LOG_ERR("Magic not found in given address");
return -ENONET;
}
handle->address = address;
handle->type = BINDESC_HANDLE_TYPE_RAM;
handle->size_limit = max_size;
return 0;
}
#endif /* IS_ENABLED(CONFIG_BINDESC_READ_RAM) */
#if IS_ENABLED(CONFIG_BINDESC_READ_FLASH)
int bindesc_open_flash(struct bindesc_handle *handle, size_t offset,
const struct device *flash_device)
{
int retval;
retval = flash_read(flash_device, offset, handle->buffer, sizeof(BINDESC_MAGIC));
if (retval) {
LOG_ERR("Flash read error: %d", retval);
return -EIO;
}
if (*(uint64_t *)handle->buffer != BINDESC_MAGIC) {
LOG_ERR("Magic not found in given address");
return -ENOENT;
}
handle->address = (uint8_t *)offset;
handle->type = BINDESC_HANDLE_TYPE_FLASH;
handle->flash_device = flash_device;
handle->size_limit = UINT16_MAX;
return 0;
}
#endif /* IS_ENABLED(CONFIG_BINDESC_READ_FLASH) */
int bindesc_foreach(struct bindesc_handle *handle, bindesc_callback_t callback, void *user_data)
{
const struct bindesc_entry *entry;
const uint8_t *address = handle->address;
int retval;
address += sizeof(BINDESC_MAGIC);
do {
retval = get_entry(handle, address, &entry);
if (retval == -EIO) {
return -EIO;
}
address += WB_UP(BINDESC_ENTRY_HEADER_SIZE + entry->len);
if (retval) {
continue;
}
retval = callback(entry, user_data);
if (retval) {
return retval;
}
} while ((entry->tag != BINDESC_TAG_DESCRIPTORS_END) &&
((address - handle->address) <= handle->size_limit));
return 0;
}
int bindesc_find_str(struct bindesc_handle *handle, uint16_t id, const char **result)
{
struct find_user_data data = {
.tag = BINDESC_TAG(STR, id),
};
if (!bindesc_foreach(handle, find_callback, &data)) {
LOG_WRN("The requested descriptor was not found");
return -ENOENT;
}
*result = (char *)data.result;
return 0;
}
int bindesc_find_uint(struct bindesc_handle *handle, uint16_t id, const uint32_t **result)
{
struct find_user_data data = {
.tag = BINDESC_TAG(UINT, id),
};
if (!bindesc_foreach(handle, find_callback, &data)) {
LOG_WRN("The requested descriptor was not found");
return -ENOENT;
}
*result = (const uint32_t *)data.result;
return 0;
}
int bindesc_find_bytes(struct bindesc_handle *handle, uint16_t id, const uint8_t **result,
size_t *result_size)
{
struct find_user_data data = {
.tag = BINDESC_TAG(BYTES, id),
};
if (!bindesc_foreach(handle, find_callback, &data)) {
LOG_WRN("The requested descriptor was not found");
return -ENOENT;
}
*result = (const uint8_t *)data.result;
*result_size = data.size;
return 0;
}
int bindesc_get_size(struct bindesc_handle *handle, size_t *result)
{
*result = sizeof(BINDESC_MAGIC);
return bindesc_foreach(handle, get_size_callback, result);
}