Add update agent implementation
Adds an implementation of the generic update agent component. This
implements the FWU state machine that ensures that a valid update
sequence is followed.
Signed-off-by: Julian Hall <julian.hall@arm.com>
Change-Id: Icf51b6527f1f84eb0f693e1629f4d2b3b075d43a
diff --git a/components/service/fwu/agent/update_agent.c b/components/service/fwu/agent/update_agent.c
new file mode 100644
index 0000000..c54e9e5
--- /dev/null
+++ b/components/service/fwu/agent/update_agent.c
@@ -0,0 +1,395 @@
+/*
+ * Copyright (c) 2022, Arm Limited. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ */
+
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+#include <common/uuid/uuid.h>
+#include <protocols/service/fwu/packed-c/status.h>
+#include <protocols/service/fwu/packed-c/fwu_proto.h>
+#include <service/fwu/fw_store/fw_store.h>
+#include <service/fwu/inspector/fw_inspector.h>
+#include "img_dir_serializer.h"
+#include "update_agent.h"
+
+
+static bool open_image_directory(
+ struct update_agent *update_agent,
+ const struct uuid_octets *uuid,
+ uint32_t *handle,
+ int *status);
+
+static bool open_fw_store_object(
+ struct update_agent *update_agent,
+ const struct uuid_octets *uuid,
+ uint32_t *handle,
+ int *status);
+
+static bool open_fw_image(
+ struct update_agent *update_agent,
+ const struct uuid_octets *uuid,
+ uint32_t *handle,
+ int *status);
+
+
+int update_agent_init(
+ struct update_agent *update_agent,
+ unsigned int boot_index,
+ fw_inspector_inspect fw_inspect_method,
+ struct fw_store *fw_store)
+{
+ assert(update_agent);
+ assert(fw_inspect_method);
+ assert(fw_store);
+
+ int status = FWU_STATUS_UNKNOWN;
+
+ update_agent->state = FWU_STATE_INITIALIZING;
+ update_agent->fw_inspect_method = fw_inspect_method;
+ update_agent->fw_store = fw_store;
+ update_agent->image_dir_buf_size = 0;
+ update_agent->image_dir_buf = NULL;
+
+ stream_manager_init(&update_agent->stream_manager);
+
+ /* Initialize and populate the fw_directory. The fw_inspector will
+ * obtain trustworthy information about the booted firmware and
+ * populate the fw_directory to reflect information about the booted
+ * firmware.
+ */
+ fw_directory_init(&update_agent->fw_directory);
+
+ status = update_agent->fw_inspect_method(
+ &update_agent->fw_directory,
+ boot_index);
+ if (status != FWU_STATUS_SUCCESS)
+ return status;
+
+ /* Allow the associated fw_store to synchronize its state to the
+ * state of the booted firmware reflected by the fw_directory.
+ */
+ status = fw_store_synchronize(
+ update_agent->fw_store,
+ &update_agent->fw_directory,
+ boot_index);
+ if (status != FWU_STATUS_SUCCESS)
+ return status;
+
+ /* Allocate a buffer for holding the serialized image directory */
+ update_agent->image_dir_buf_size = img_dir_serializer_get_len(&update_agent->fw_directory);
+ update_agent->image_dir_buf = malloc(update_agent->image_dir_buf_size);
+ if (!update_agent->image_dir_buf)
+ return FWU_STATUS_UNKNOWN;
+
+ /* Transition to initial state */
+ update_agent->state =
+ fw_store_is_trial(update_agent->fw_store) ?
+ FWU_STATE_TRIAL : FWU_STATE_REGULAR;
+
+ return FWU_STATUS_SUCCESS;
+}
+
+void update_agent_deinit(
+ struct update_agent *update_agent)
+{
+ update_agent->state = FWU_STATE_DEINITIALZED;
+
+ free(update_agent->image_dir_buf);
+ fw_directory_deinit(&update_agent->fw_directory);
+ stream_manager_deinit(&update_agent->stream_manager);
+}
+
+int update_agent_begin_staging(
+ struct update_agent *update_agent)
+{
+ int status = FWU_STATUS_DENIED;
+
+ /* If already staging, any previous installation state is discarded */
+ update_agent_cancel_staging(update_agent);
+
+ if (update_agent->state == FWU_STATE_REGULAR) {
+
+ status = fw_store_begin_install(update_agent->fw_store);
+
+ /* Check if ready to install images */
+ if (status == FWU_STATUS_SUCCESS)
+ update_agent->state = FWU_STATE_STAGING;
+ }
+
+ return status;
+}
+
+int update_agent_end_staging(
+ struct update_agent *update_agent)
+{
+ int status = FWU_STATUS_DENIED;
+
+ if (update_agent->state == FWU_STATE_STAGING) {
+
+ /* The client is responsible for committing each installed image. If any
+ * install streams have been left open, not all images were committed.
+ */
+ bool any_uncommitted = stream_manager_is_open_streams(
+ &update_agent->stream_manager, FWU_STREAM_TYPE_INSTALL);
+
+ if (!any_uncommitted) {
+
+ /* All installed images have been committed so we're
+ * ready for a trial.
+ */
+ status = fw_store_finalize_install(update_agent->fw_store);
+
+ if (status == FWU_STATUS_SUCCESS)
+ /* Transition to TRAIL_PENDING state. The trial actually starts
+ * when installed images are activated through a system restart.
+ */
+ update_agent->state = FWU_STATE_TRIAL_PENDING;
+
+ } else {
+
+ /* Client failed to commit all images installed */
+ status = FWU_STATUS_BUSY;
+ }
+ }
+
+ return status;
+}
+
+int update_agent_cancel_staging(
+ struct update_agent *update_agent)
+{
+ int status = FWU_STATUS_DENIED;
+
+ if (update_agent->state == FWU_STATE_STAGING) {
+
+ stream_manager_cancel_streams(
+ &update_agent->stream_manager, FWU_STREAM_TYPE_INSTALL);
+
+ fw_store_cancel_install(update_agent->fw_store);
+
+ update_agent->state = FWU_STATE_REGULAR;
+
+ status = FWU_STATUS_SUCCESS;
+ }
+
+ return status;
+}
+
+int update_agent_accept(
+ struct update_agent *update_agent,
+ const struct uuid_octets *image_type_uuid)
+{
+ int status = FWU_STATUS_DENIED;
+
+ if (update_agent->state == FWU_STATE_TRIAL) {
+
+ const struct image_info *image_info = fw_directory_find_image_info(
+ &update_agent->fw_directory,
+ image_type_uuid);
+
+ if (image_info) {
+
+ if (fw_store_notify_accepted(
+ update_agent->fw_store, image_info)) {
+
+ /* From the fw_store perspective, the update has
+ * been fully accepted.
+ */
+ status = fw_store_commit_to_update(update_agent->fw_store);
+ update_agent->state = FWU_STATE_REGULAR;
+
+ } else
+ /* Still more images to accept */
+ status = FWU_STATUS_SUCCESS;
+ } else
+ /* Unrecognised image uuid */
+ status = FWU_STATUS_UNKNOWN;
+ }
+
+ return status;
+}
+
+int update_agent_select_previous(
+ struct update_agent *update_agent)
+{
+ int status = FWU_STATUS_DENIED;
+
+ if ((update_agent->state == FWU_STATE_TRIAL) ||
+ (update_agent->state == FWU_STATE_TRIAL_PENDING)) {
+
+ status = fw_store_revert_to_previous(update_agent->fw_store);
+ update_agent->state = FWU_STATE_REGULAR;
+ }
+
+ return status;
+}
+
+int update_agent_open(
+ struct update_agent *update_agent,
+ const struct uuid_octets *uuid,
+ uint32_t *handle)
+{
+ int status;
+
+ /* Pass UUID along a chain-of-responsibility until it's handled */
+ if (!open_image_directory(update_agent, uuid, handle, &status) &&
+ !open_fw_store_object(update_agent, uuid, handle, &status) &&
+ !open_fw_image(update_agent, uuid, handle, &status)) {
+
+ /* UUID not recognised */
+ status = FWU_STATUS_UNKNOWN;
+ }
+
+ return status;
+}
+
+int update_agent_commit(
+ struct update_agent *update_agent,
+ uint32_t handle,
+ bool accepted)
+{
+ return stream_manager_close(
+ &update_agent->stream_manager,
+ handle,
+ accepted);
+}
+
+int update_agent_write_stream(
+ struct update_agent *update_agent,
+ uint32_t handle,
+ const uint8_t *data,
+ size_t data_len)
+{
+ return stream_manager_write(
+ &update_agent->stream_manager,
+ handle,
+ data, data_len);
+}
+
+int update_agent_read_stream(
+ struct update_agent *update_agent,
+ uint32_t handle,
+ uint8_t *buf,
+ size_t buf_size,
+ size_t *read_len,
+ size_t *total_len)
+{
+ return stream_manager_read(
+ &update_agent->stream_manager,
+ handle,
+ buf, buf_size,
+ read_len, total_len);
+}
+
+static bool open_image_directory(
+ struct update_agent *update_agent,
+ const struct uuid_octets *uuid,
+ uint32_t *handle,
+ int *status)
+{
+ struct uuid_octets target_uuid;
+
+ uuid_guid_octets_from_canonical(&target_uuid, FWU_DIRECTORY_CANONICAL_UUID);
+
+ if (uuid_is_equal(uuid->octets, target_uuid.octets)) {
+
+ /* Serialize a fresh view of the image directory */
+ size_t serialized_len = 0;
+
+ *status = img_dir_serializer_serialize(
+ &update_agent->fw_directory,
+ update_agent->fw_store,
+ update_agent->image_dir_buf,
+ update_agent->image_dir_buf_size,
+ &serialized_len);
+
+ if (*status == FWU_STATUS_SUCCESS) {
+
+ *status = stream_manager_open_buffer_stream(
+ &update_agent->stream_manager,
+ update_agent->image_dir_buf,
+ serialized_len,
+ handle);
+ }
+
+ return true;
+ }
+
+ return false;
+}
+
+static bool open_fw_store_object(
+ struct update_agent *update_agent,
+ const struct uuid_octets *uuid,
+ uint32_t *handle,
+ int *status)
+{
+ const uint8_t *exported_data;
+ size_t exported_data_len;
+
+ if (fw_store_export(
+ update_agent->fw_store,
+ uuid,
+ &exported_data, &exported_data_len,
+ status)) {
+
+ if (*status == FWU_STATUS_SUCCESS) {
+
+ *status = stream_manager_open_buffer_stream(
+ &update_agent->stream_manager,
+ exported_data,
+ exported_data_len,
+ handle);
+ }
+
+ return true;
+ }
+
+ return false;
+}
+
+static bool open_fw_image(
+ struct update_agent *update_agent,
+ const struct uuid_octets *uuid,
+ uint32_t *handle,
+ int *status)
+{
+ const struct image_info *image_info = fw_directory_find_image_info(
+ &update_agent->fw_directory,
+ uuid);
+
+ if (image_info) {
+
+ if (update_agent->state == FWU_STATE_STAGING) {
+
+ struct installer *installer;
+
+ *status = fw_store_select_installer(
+ update_agent->fw_store,
+ image_info,
+ &installer);
+
+ if (*status == FWU_STATUS_SUCCESS) {
+
+ *status = stream_manager_open_install_stream(
+ &update_agent->stream_manager,
+ update_agent->fw_store,
+ installer,
+ image_info,
+ handle);
+ }
+ } else {
+
+ /* Attempting to open a fw image when not staging */
+ *status = FWU_STATUS_DENIED;
+ }
+
+ return true;
+ }
+
+ return false;
+}