blob: bd7365f3874d59dbb7911370d073e6fc43893499 [file] [log] [blame]
/*
* Copyright (c) 2022, Arm Limited and Contributors. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include "stream_manager.h"
#include <stddef.h>
#include <string.h>
#include "protocols/service/fwu/status.h"
#include "service/fwu/fw_store/fw_store.h"
static uint32_t generate_handle(struct stream_manager *subject,
const struct stream_context *const context)
{
/* Handle includes rolling count value to protect against use of a stale handle */
uint32_t new_handle = context - subject->contexts;
new_handle = (new_handle & 0xffff) | (subject->rolling_count << 16);
++subject->rolling_count;
return new_handle;
}
static uint32_t index_from_handle(uint32_t handle)
{
return handle & 0xffff;
}
static void add_to_free_list(struct stream_manager *subject, struct stream_context *context)
{
context->type = FWU_STREAM_TYPE_NONE;
context->handle = 0;
context->next = subject->free;
context->prev = NULL;
subject->free = context;
}
static struct stream_context *alloc_stream_context(struct stream_manager *subject,
enum fwu_stream_type type, uint32_t *handle)
{
struct stream_context *context = NULL;
/* Re-cycle least-recently used context if there are no free contexts */
if (!subject->free && subject->active_tail) {
stream_manager_close(subject, subject->active_tail->handle, false);
}
/* Active contexts are held in a linked list in most recently allocated order */
if (subject->free) {
context = subject->free;
subject->free = context->next;
context->next = subject->active_head;
context->prev = NULL;
subject->active_head = context;
if (!subject->active_tail)
subject->active_tail = context;
if (context->next)
context->next->prev = context;
context->type = type;
context->handle = generate_handle(subject, context);
*handle = context->handle;
}
return context;
}
static void free_stream_context(struct stream_manager *subject, struct stream_context *context)
{
/* Remove from active list */
if (context->prev)
context->prev->next = context->next;
else
subject->active_head = context->next;
if (context->next)
context->next->prev = context->prev;
else
subject->active_tail = context->prev;
/* Add to free list */
add_to_free_list(subject, context);
}
static struct stream_context *get_active_context(struct stream_manager *subject, uint32_t handle)
{
struct stream_context *context = NULL;
uint32_t index = index_from_handle(handle);
if ((index < FWU_STREAM_MANAGER_POOL_SIZE) &&
(subject->contexts[index].type != FWU_STREAM_TYPE_NONE) &&
(subject->contexts[index].handle == handle)) {
/* Handle qualifies an active stream context */
context = &subject->contexts[index];
}
return context;
}
void stream_manager_init(struct stream_manager *subject)
{
subject->free = NULL;
subject->active_head = NULL;
subject->active_tail = NULL;
subject->rolling_count = 0;
for (size_t i = 0; i < FWU_STREAM_MANAGER_POOL_SIZE; i++)
add_to_free_list(subject, &subject->contexts[i]);
}
void stream_manager_deinit(struct stream_manager *subject)
{
(void)subject;
}
int stream_manager_open_buffer_stream(struct stream_manager *subject, const uint8_t *data,
size_t data_len, uint32_t *handle)
{
struct stream_context *context;
int status = FWU_STATUS_UNKNOWN;
/* First cancel any buffer streams left open associated with
* the same source buffer. Concurrent stream access to data in
* a buffer is prevented to avoid the possibility of a buffer
* being updated while a read stream is open.
*/
for (size_t i = 0; i < FWU_STREAM_MANAGER_POOL_SIZE; i++) {
context = &subject->contexts[i];
if ((context->type == FWU_STREAM_TYPE_BUFFER) &&
(context->variant.buffer.data == data))
free_stream_context(subject, context);
}
/* Allocate and initialize a new stream */
context = alloc_stream_context(subject, FWU_STREAM_TYPE_BUFFER, handle);
if (context) {
context->variant.buffer.data = data;
context->variant.buffer.data_len = data_len;
context->variant.buffer.pos = 0;
status = FWU_STATUS_SUCCESS;
}
return status;
}
int stream_manager_open_install_stream(struct stream_manager *subject, struct fw_store *fw_store,
struct installer *installer,
const struct image_info *image_info, uint32_t *stream_handle)
{
struct stream_context *context;
int status = FWU_STATUS_UNKNOWN;
/* First cancel any install streams left open associated with
* the same fw_store and installer. This defends against the
* possibility of data written via two streams being written to the
* same installer, resulting in image corruption.
*/
for (size_t i = 0; i < FWU_STREAM_MANAGER_POOL_SIZE; i++) {
context = &subject->contexts[i];
if ((context->type == FWU_STREAM_TYPE_INSTALL) &&
(context->variant.install.fw_store == fw_store) &&
(context->variant.install.installer == installer))
free_stream_context(subject, context);
}
/* Allocate and initialize a new stream */
context = alloc_stream_context(subject, FWU_STREAM_TYPE_INSTALL, stream_handle);
if (context) {
context->variant.install.fw_store = fw_store;
context->variant.install.installer = installer;
context->variant.install.image_info = image_info;
status = FWU_STATUS_SUCCESS;
}
return status;
}
int stream_manager_close(struct stream_manager *subject, uint32_t handle, bool accepted)
{
int status = FWU_STATUS_UNKNOWN;
struct stream_context *context = get_active_context(subject, handle);
if (context) {
status = FWU_STATUS_SUCCESS;
if (context->type == FWU_STREAM_TYPE_INSTALL) {
status = fw_store_commit_image(context->variant.install.fw_store,
context->variant.install.installer,
context->variant.install.image_info,
accepted);
}
free_stream_context(subject, context);
}
return status;
}
void stream_manager_cancel_streams(struct stream_manager *subject, enum fwu_stream_type type)
{
for (size_t i = 0; i < FWU_STREAM_MANAGER_POOL_SIZE; i++) {
struct stream_context *context = &subject->contexts[i];
if (context->type == type)
free_stream_context(subject, context);
}
}
bool stream_manager_is_open_streams(const struct stream_manager *subject, enum fwu_stream_type type)
{
bool any_open = false;
for (size_t i = 0; i < FWU_STREAM_MANAGER_POOL_SIZE; i++) {
const struct stream_context *context = &subject->contexts[i];
if (context->type == type) {
any_open = true;
break;
}
}
return any_open;
}
int stream_manager_write(struct stream_manager *subject, uint32_t handle, const uint8_t *data,
size_t data_len)
{
int status = FWU_STATUS_UNKNOWN;
struct stream_context *context = get_active_context(subject, handle);
if (context && context->type == FWU_STREAM_TYPE_INSTALL) {
status = fw_store_write_image(context->variant.install.fw_store,
context->variant.install.installer, data, data_len);
}
return status;
}
int stream_manager_read(struct stream_manager *subject, uint32_t handle, uint8_t *buf,
size_t buf_size, size_t *read_len, size_t *total_len)
{
int status = FWU_STATUS_UNKNOWN;
struct stream_context *context = get_active_context(subject, handle);
if (context) {
if (context->type == FWU_STREAM_TYPE_BUFFER) {
size_t pos = context->variant.buffer.pos;
size_t remaining_len = context->variant.buffer.data_len - pos;
size_t len_to_read = (remaining_len <= buf_size) ? remaining_len : buf_size;
memcpy(buf, &context->variant.buffer.data[pos], len_to_read);
*read_len = len_to_read;
*total_len = context->variant.buffer.data_len;
context->variant.buffer.pos = pos + len_to_read;
status = FWU_STATUS_SUCCESS;
} else {
/* Reading from other types of stream is forbidden */
status = FWU_STATUS_DENIED;
}
}
return status;
}