| /* |
| * Copyright (c) 2022, Arm Limited. All rights reserved. |
| * |
| * SPDX-License-Identifier: BSD-3-Clause |
| * |
| */ |
| |
| #include "update_agent.h" |
| |
| #include <assert.h> |
| #include <stdlib.h> |
| #include <string.h> |
| |
| #include "common/uuid/uuid.h" |
| #include "img_dir_serializer.h" |
| #include "protocols/service/fwu/packed-c/fwu_proto.h" |
| #include "protocols/service/fwu/packed-c/status.h" |
| #include "service/fwu/fw_store/fw_store.h" |
| #include "service/fwu/inspector/fw_inspector.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; |
| } |