libpldm: Add firmware update FD responder
This provides a FD responder implementation, with an ops struct to allow
for platform customization. Applications provide incoming messages to
pldm_fd_handle_msg(), and periodically call pldm_fd_progress() for
asynchronous events.
Change-Id: I034262e8b2c45b5bea61369d5f966dd7e530ba9e
Signed-off-by: Matt Johnston <matt@codeconstruct.com.au>
diff --git a/src/firmware_device/fd.c b/src/firmware_device/fd.c
new file mode 100644
index 0000000..116657c
--- /dev/null
+++ b/src/firmware_device/fd.c
@@ -0,0 +1,1422 @@
+/* SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later */
+#include <stdint.h>
+#include <stdbool.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include <libpldm/pldm.h>
+#include <libpldm/firmware_update.h>
+#include <libpldm/firmware_fd.h>
+#include <libpldm/utils.h>
+#include <compiler.h>
+#include <msgbuf.h>
+
+#include "fd-internal.h"
+
+/* FD_T1 Update mode idle timeout, 120 seconds (range [60s, 120s])*/
+static const pldm_fd_time_t DEFAULT_FD_T1_TIMEOUT = 120000;
+
+/* FD_T2 "Retry request for firmware data", 1 second (range [1s, 5s]) */
+static const pldm_fd_time_t DEFAULT_FD_T2_RETRY_TIME = 1000;
+
+static const uint8_t INSTANCE_ID_COUNT = 32;
+static const uint8_t PROGRESS_PERCENT_NOT_SUPPORTED = 101;
+
+/* Ensure that public definition is kept updated */
+static_assert(alignof(struct pldm_fd) == PLDM_ALIGNOF_PLDM_FD,
+ "PLDM_ALIGNOF_PLDM_FD wrong");
+
+/* Maybe called with success or failure completion codes, though
+ * success only makes sense for responses without a body.
+ * Returns 0 or negative errno. */
+LIBPLDM_CC_NONNULL
+static int pldm_fd_reply_cc(uint8_t ccode,
+ const struct pldm_header_info *req_hdr,
+ struct pldm_msg *resp, size_t *resp_payload_len)
+{
+ int status;
+
+ /* 1 byte completion code */
+ if (*resp_payload_len < 1) {
+ return -EOVERFLOW;
+ }
+ *resp_payload_len = 1;
+
+ status = encode_cc_only_resp(req_hdr->instance, PLDM_FWUP,
+ req_hdr->command, ccode, resp);
+ if (status != PLDM_SUCCESS) {
+ return -EINVAL;
+ }
+ return 0;
+}
+
+/* Must be called with a negative errno.
+ * Returns 0 or negative errno. */
+LIBPLDM_CC_NONNULL
+static int pldm_fd_reply_errno(int err, const struct pldm_header_info *req_hdr,
+ struct pldm_msg *resp, size_t *resp_payload_len)
+{
+ uint8_t ccode = PLDM_ERROR;
+
+ assert(err < 0);
+ switch (err) {
+ case -EINVAL:
+ // internal error, shouldn't occur.
+ ccode = PLDM_ERROR;
+ break;
+ case -EPROTO:
+ // Bad data from peer
+ ccode = PLDM_ERROR_INVALID_DATA;
+ break;
+ case -EOVERFLOW:
+ case -EBADMSG:
+ // Bad data from peer
+ ccode = PLDM_ERROR_INVALID_LENGTH;
+ break;
+ default:
+ // general error
+ ccode = PLDM_ERROR;
+ }
+
+ return pldm_fd_reply_cc(ccode, req_hdr, resp, resp_payload_len);
+}
+
+LIBPLDM_CC_NONNULL
+static void pldm_fd_set_state(struct pldm_fd *fd,
+ enum pldm_firmware_device_states state)
+{
+ /* pldm_fd_set_idle should be used instead */
+ assert(state != PLDM_FD_STATE_IDLE);
+
+ if (fd->state == state) {
+ return;
+ }
+
+ fd->prev_state = fd->state;
+ fd->state = state;
+}
+
+LIBPLDM_CC_NONNULL
+static void pldm_fd_set_idle(struct pldm_fd *fd,
+ enum pldm_get_status_reason_code_values reason)
+{
+ fd->prev_state = fd->state;
+ fd->state = PLDM_FD_STATE_IDLE;
+ fd->reason = reason;
+ fd->ua_address_set = false;
+}
+
+LIBPLDM_CC_NONNULL
+static void pldm_fd_idle_timeout(struct pldm_fd *fd)
+{
+ enum pldm_get_status_reason_code_values reason = PLDM_FD_INITIALIZATION;
+
+ switch (fd->state) {
+ case PLDM_FD_STATE_IDLE:
+ return;
+ case PLDM_FD_STATE_LEARN_COMPONENTS:
+ reason = PLDM_FD_TIMEOUT_LEARN_COMPONENT;
+ break;
+ case PLDM_FD_STATE_READY_XFER:
+ reason = PLDM_FD_TIMEOUT_READY_XFER;
+ break;
+ case PLDM_FD_STATE_DOWNLOAD:
+ reason = PLDM_FD_TIMEOUT_DOWNLOAD;
+ break;
+ case PLDM_FD_STATE_VERIFY:
+ reason = PLDM_FD_TIMEOUT_VERIFY;
+ break;
+ case PLDM_FD_STATE_APPLY:
+ reason = PLDM_FD_TIMEOUT_APPLY;
+ break;
+ case PLDM_FD_STATE_ACTIVATE:
+ /* Not a timeout */
+ reason = PLDM_FD_ACTIVATE_FW;
+ break;
+ }
+
+ pldm_fd_set_idle(fd, reason);
+}
+
+LIBPLDM_CC_NONNULL
+static void pldm_fd_get_aux_state(const struct pldm_fd *fd, uint8_t *aux_state,
+ uint8_t *aux_state_status)
+{
+ *aux_state_status = 0;
+
+ switch (fd->req.state) {
+ case PLDM_FD_REQ_UNUSED:
+ *aux_state = PLDM_FD_IDLE_LEARN_COMPONENTS_READ_XFER;
+ break;
+ case PLDM_FD_REQ_SENT:
+ *aux_state = PLDM_FD_OPERATION_IN_PROGRESS;
+ break;
+ case PLDM_FD_REQ_READY:
+ if (fd->req.complete) {
+ *aux_state = PLDM_FD_OPERATION_SUCCESSFUL;
+ } else {
+ *aux_state = PLDM_FD_OPERATION_IN_PROGRESS;
+ }
+ break;
+ case PLDM_FD_REQ_FAILED:
+ *aux_state = PLDM_FD_OPERATION_FAILED;
+ *aux_state_status = fd->req.result;
+ break;
+ }
+}
+
+LIBPLDM_CC_NONNULL
+static uint64_t pldm_fd_now(struct pldm_fd *fd)
+{
+ return fd->ops->now(fd->ops_ctx);
+}
+
+LIBPLDM_CC_NONNULL
+static bool pldm_fd_req_should_send(struct pldm_fd *fd)
+{
+ pldm_fd_time_t now = pldm_fd_now(fd);
+
+ switch (fd->req.state) {
+ case PLDM_FD_REQ_UNUSED:
+ assert(false);
+ return false;
+ case PLDM_FD_REQ_READY:
+ return true;
+ case PLDM_FD_REQ_FAILED:
+ return false;
+ case PLDM_FD_REQ_SENT:
+ if (now < fd->req.sent_time) {
+ /* Time went backwards */
+ return false;
+ }
+
+ /* Send if retry time has elapsed */
+ return (now - fd->req.sent_time) >= fd->fd_t2_retry_time;
+ }
+ return false;
+}
+
+/* Allocate the next instance ID. Only one request is outstanding so cycling
+ * through the range is OK */
+LIBPLDM_CC_NONNULL
+static uint8_t pldm_fd_req_next_instance(struct pldm_fd_req *req)
+{
+ req->instance_id = (req->instance_id + 1) % INSTANCE_ID_COUNT;
+ return req->instance_id;
+}
+
+LIBPLDM_CC_NONNULL
+static int pldm_fd_qdi(struct pldm_fd *fd, const struct pldm_header_info *hdr,
+ const struct pldm_msg *req LIBPLDM_CC_UNUSED,
+ size_t req_payload_len, struct pldm_msg *resp,
+ size_t *resp_payload_len)
+{
+ uint8_t descriptor_count;
+ const struct pldm_descriptor *descriptors;
+ int rc;
+
+ /* QDI has no request data */
+ if (req_payload_len != PLDM_QUERY_DEVICE_IDENTIFIERS_REQ_BYTES) {
+ return pldm_fd_reply_cc(PLDM_ERROR_INVALID_LENGTH, hdr, resp,
+ resp_payload_len);
+ }
+
+ /* Retrieve platform-specific data */
+ rc = fd->ops->device_identifiers(fd->ops_ctx, &descriptor_count,
+ &descriptors);
+ if (rc) {
+ return pldm_fd_reply_cc(PLDM_ERROR, hdr, resp,
+ resp_payload_len);
+ }
+
+ rc = encode_query_device_identifiers_resp(hdr->instance,
+ descriptor_count, descriptors,
+ resp, resp_payload_len);
+
+ if (rc) {
+ return pldm_fd_reply_errno(rc, hdr, resp, resp_payload_len);
+ }
+
+ return 0;
+}
+
+LIBPLDM_CC_NONNULL
+static int pldm_fd_fw_param(struct pldm_fd *fd,
+ const struct pldm_header_info *hdr,
+ const struct pldm_msg *req LIBPLDM_CC_UNUSED,
+ size_t req_payload_len, struct pldm_msg *resp,
+ size_t *resp_payload_len)
+{
+ uint16_t entry_count;
+ const struct pldm_firmware_component_standalone **entries;
+ struct pldm_msgbuf _buf;
+ struct pldm_msgbuf *buf = &_buf;
+ int rc;
+
+ /* No request data */
+ if (req_payload_len != PLDM_GET_FIRMWARE_PARAMETERS_REQ_BYTES) {
+ return pldm_fd_reply_cc(PLDM_ERROR_INVALID_LENGTH, hdr, resp,
+ resp_payload_len);
+ }
+
+ /* Retrieve platform-specific data */
+ rc = fd->ops->components(fd->ops_ctx, &entry_count, &entries);
+ if (rc) {
+ return pldm_fd_reply_cc(PLDM_ERROR, hdr, resp,
+ resp_payload_len);
+ }
+
+ rc = pldm_msgbuf_init_errno(buf, 0, resp->payload, *resp_payload_len);
+ if (rc) {
+ return rc;
+ }
+
+ /* Add the fixed parameters */
+ {
+ struct pldm_get_firmware_parameters_resp_full fwp = {
+ .completion_code = PLDM_SUCCESS,
+ // TODO defaulted to 0, could have a callback.
+ .capabilities_during_update = { 0 },
+ .comp_count = entry_count,
+ };
+ /* fill active and pending strings */
+ rc = fd->ops->imageset_versions(
+ fd->ops_ctx, &fwp.active_comp_image_set_ver_str,
+ &fwp.pending_comp_image_set_ver_str);
+ if (rc) {
+ return pldm_fd_reply_cc(PLDM_ERROR, hdr, resp,
+ resp_payload_len);
+ }
+
+ size_t len = buf->remaining;
+ rc = encode_get_firmware_parameters_resp(hdr->instance, &fwp,
+ resp, &len);
+ if (rc) {
+ return pldm_fd_reply_errno(rc, hdr, resp,
+ resp_payload_len);
+ }
+ rc = pldm_msgbuf_skip(buf, len);
+ if (rc) {
+ return rc;
+ }
+ }
+
+ /* Add the component table entries */
+ for (uint16_t i = 0; i < entry_count; i++) {
+ const struct pldm_firmware_component_standalone *e = entries[i];
+ void *out = NULL;
+ size_t len;
+
+ struct pldm_component_parameter_entry_full comp = {
+ .comp_classification = e->comp_classification,
+ .comp_identifier = e->comp_identifier,
+ .comp_classification_index =
+ e->comp_classification_index,
+
+ .active_ver = e->active_ver,
+ .pending_ver = e->pending_ver,
+
+ .comp_activation_methods = e->comp_activation_methods,
+ .capabilities_during_update =
+ e->capabilities_during_update,
+ };
+
+ if (pldm_msgbuf_peek_remaining(buf, &out, &len)) {
+ return rc;
+ }
+ rc = encode_get_firmware_parameters_resp_comp_entry(&comp, out,
+ &len);
+ if (rc) {
+ return pldm_fd_reply_errno(rc, hdr, resp,
+ resp_payload_len);
+ }
+ rc = pldm_msgbuf_skip(buf, len);
+ if (rc) {
+ return rc;
+ }
+ }
+
+ return pldm_msgbuf_destroy_used(buf, *resp_payload_len,
+ resp_payload_len);
+}
+
+LIBPLDM_CC_NONNULL
+static int pldm_fd_request_update(struct pldm_fd *fd,
+ const struct pldm_header_info *hdr,
+ const struct pldm_msg *req,
+ size_t req_payload_len, struct pldm_msg *resp,
+ size_t *resp_payload_len, uint8_t address)
+{
+ struct pldm_request_update_req_full upd;
+ const struct pldm_request_update_resp resp_data = {
+ .fd_meta_data_len = 0,
+ .fd_will_send_pkg_data = 0,
+ };
+ int rc;
+
+ if (fd->state != PLDM_FD_STATE_IDLE) {
+ return pldm_fd_reply_cc(PLDM_FWUP_ALREADY_IN_UPDATE_MODE, hdr,
+ resp, resp_payload_len);
+ }
+
+ rc = decode_request_update_req(req, req_payload_len, &upd);
+ if (rc) {
+ return pldm_fd_reply_errno(rc, hdr, resp, resp_payload_len);
+ }
+
+ /* No metadata nor pkg data */
+ rc = encode_request_update_resp(hdr->instance, &resp_data, resp,
+ resp_payload_len);
+ if (rc) {
+ return pldm_fd_reply_errno(rc, hdr, resp, resp_payload_len);
+ }
+
+ // TODO pass num_of_comp and image_set_ver to application?
+
+ fd->max_transfer =
+ fd->ops->transfer_size(fd->ops_ctx, upd.max_transfer_size);
+ if (fd->max_transfer > upd.max_transfer_size) {
+ /* Limit to UA's size */
+ fd->max_transfer = upd.max_transfer_size;
+ }
+ if (fd->max_transfer < PLDM_FWUP_BASELINE_TRANSFER_SIZE) {
+ /* Don't let it be zero, that will loop forever */
+ fd->max_transfer = PLDM_FWUP_BASELINE_TRANSFER_SIZE;
+ }
+ fd->ua_address = address;
+ fd->ua_address_set = true;
+
+ pldm_fd_set_state(fd, PLDM_FD_STATE_LEARN_COMPONENTS);
+
+ return 0;
+}
+
+/* wrapper around ops->cancel, will only run ops->cancel when a component update is
+ * active */
+LIBPLDM_CC_NONNULL
+static void pldm_fd_maybe_cancel_component(struct pldm_fd *fd)
+{
+ bool cancel = false;
+
+ switch (fd->state) {
+ case PLDM_FD_STATE_DOWNLOAD:
+ case PLDM_FD_STATE_VERIFY:
+ cancel = true;
+ break;
+ case PLDM_FD_STATE_APPLY:
+ /* In apply state, once the application ops->apply() has completed
+ * successfully the component is no longer in update state.
+ * In that case the cancel should not be forwarded to the application.
+ * This can occur if a cancel is received while waiting for the
+ * response to a success ApplyComplete. */
+ cancel = !(fd->req.complete &&
+ fd->req.result == PLDM_FWUP_APPLY_SUCCESS);
+ break;
+ default:
+ break;
+ }
+
+ if (cancel) {
+ /* Call the platform handler for the current component in progress */
+ fd->ops->cancel_update_component(fd->ops_ctx, &fd->update_comp);
+ }
+}
+
+/* Wrapper around ops->update_component() that first checks that the component
+ * is in the list returned from ops->components() */
+LIBPLDM_CC_NONNULL
+static enum pldm_component_response_codes pldm_fd_check_update_component(
+ struct pldm_fd *fd, bool update,
+ const struct pldm_firmware_update_component *comp)
+{
+ bool found;
+ uint16_t entry_count;
+ int rc;
+
+ const struct pldm_firmware_component_standalone **entries;
+ rc = fd->ops->components(fd->ops_ctx, &entry_count, &entries);
+ if (rc) {
+ return PLDM_CRC_COMP_NOT_SUPPORTED;
+ }
+
+ found = false;
+ for (uint16_t i = 0; i < entry_count; i++) {
+ if (entries[i]->comp_classification ==
+ comp->comp_classification &&
+ entries[i]->comp_identifier == comp->comp_identifier &&
+ entries[i]->comp_classification_index ==
+ comp->comp_classification_index) {
+ found = true;
+ break;
+ }
+ }
+ if (found) {
+ return fd->ops->update_component(fd->ops_ctx, update, comp);
+ }
+ return PLDM_CRC_COMP_NOT_SUPPORTED;
+}
+
+LIBPLDM_CC_NONNULL
+static int pldm_fd_pass_comp(struct pldm_fd *fd,
+ const struct pldm_header_info *hdr,
+ const struct pldm_msg *req, size_t req_payload_len,
+ struct pldm_msg *resp, size_t *resp_payload_len)
+{
+ struct pldm_pass_component_table_req_full pcomp;
+ uint8_t comp_response_code;
+ int rc;
+
+ if (fd->state != PLDM_FD_STATE_LEARN_COMPONENTS) {
+ return pldm_fd_reply_cc(PLDM_FWUP_INVALID_STATE_FOR_COMMAND,
+ hdr, resp, resp_payload_len);
+ }
+
+ /* fd->update_comp is used as temporary storage during PassComponent validation */
+ /* Some portions are unused for PassComponentTable */
+ fd->update_comp.comp_image_size = 0;
+ fd->update_comp.update_option_flags.value = 0;
+
+ rc = decode_pass_component_table_req(req, req_payload_len, &pcomp);
+ if (rc) {
+ return pldm_fd_reply_errno(rc, hdr, resp, resp_payload_len);
+ }
+
+ fd->update_comp.comp_classification = pcomp.comp_classification;
+ fd->update_comp.comp_identifier = pcomp.comp_identifier;
+ fd->update_comp.comp_classification_index =
+ pcomp.comp_classification_index;
+ fd->update_comp.comp_comparison_stamp = pcomp.comp_comparison_stamp;
+ memcpy(&fd->update_comp.version, &pcomp.version, sizeof(pcomp.version));
+
+ comp_response_code =
+ pldm_fd_check_update_component(fd, false, &fd->update_comp);
+
+ const struct pldm_pass_component_table_resp resp_data = {
+ /* Component Response Code is 0 for ComponentResponse, 1 otherwise */
+ .comp_resp = (comp_response_code != 0),
+ .comp_resp_code = comp_response_code,
+ };
+
+ rc = encode_pass_component_table_resp(hdr->instance, &resp_data, resp,
+ resp_payload_len);
+ if (rc) {
+ return pldm_fd_reply_errno(rc, hdr, resp, resp_payload_len);
+ }
+
+ if (pcomp.transfer_flag & PLDM_END) {
+ pldm_fd_set_state(fd, PLDM_FD_STATE_READY_XFER);
+ }
+
+ return 0;
+}
+
+LIBPLDM_CC_NONNULL
+static int pldm_fd_update_comp(struct pldm_fd *fd,
+ const struct pldm_header_info *hdr,
+ const struct pldm_msg *req,
+ size_t req_payload_len, struct pldm_msg *resp,
+ size_t *resp_payload_len)
+{
+ struct pldm_update_component_req_full up;
+ uint8_t comp_response_code;
+ int rc;
+
+ if (fd->state != PLDM_FD_STATE_READY_XFER) {
+ return pldm_fd_reply_cc(PLDM_FWUP_INVALID_STATE_FOR_COMMAND,
+ hdr, resp, resp_payload_len);
+ }
+
+ rc = decode_update_component_req(req, req_payload_len, &up);
+ if (rc) {
+ return pldm_fd_reply_errno(rc, hdr, resp, resp_payload_len);
+ }
+
+ /* Store update_comp to pass to further callbacks. This persists
+ * until the component update completes or is cancelled */
+ fd->update_comp.comp_classification = up.comp_classification;
+ fd->update_comp.comp_identifier = up.comp_identifier;
+ fd->update_comp.comp_classification_index =
+ up.comp_classification_index;
+ fd->update_comp.comp_comparison_stamp = up.comp_comparison_stamp;
+ fd->update_comp.comp_image_size = up.comp_image_size;
+ fd->update_comp.update_option_flags = up.update_option_flags;
+ memcpy(&fd->update_comp.version, &up.version, sizeof(up.version));
+
+ comp_response_code =
+ pldm_fd_check_update_component(fd, true, &fd->update_comp);
+
+ // Mask to only the "Force Update" flag, others are not handled.
+ bitfield32_t update_flags = {
+ .bits.bit0 = fd->update_comp.update_option_flags.bits.bit0
+ };
+
+ const struct pldm_update_component_resp resp_data = {
+ /* Component Response Code is 0 for ComponentResponse, 1 otherwise */
+ .comp_compatibility_resp = (comp_response_code != 0),
+ .comp_compatibility_resp_code = comp_response_code,
+ .update_option_flags_enabled = update_flags,
+ .time_before_req_fw_data = 0,
+ };
+
+ rc = encode_update_component_resp(hdr->instance, &resp_data, resp,
+ resp_payload_len);
+ if (rc) {
+ /* Encoding response failed */
+ if (comp_response_code == PLDM_CRC_COMP_CAN_BE_UPDATED) {
+ /* Inform the application of cancellation. Call it directly
+ * rather than going through pldm_fd_maybe_cancel_component() */
+ fd->ops->cancel_update_component(fd->ops_ctx,
+ &fd->update_comp);
+ }
+ return pldm_fd_reply_errno(rc, hdr, resp, resp_payload_len);
+ }
+
+ /* Set up download state */
+ if (comp_response_code == PLDM_CRC_COMP_CAN_BE_UPDATED) {
+ memset(&fd->specific, 0x0, sizeof(fd->specific));
+ fd->update_flags = update_flags;
+ fd->req.state = PLDM_FD_REQ_READY;
+ fd->req.complete = false;
+ pldm_fd_set_state(fd, PLDM_FD_STATE_DOWNLOAD);
+ }
+
+ return 0;
+}
+
+LIBPLDM_CC_NONNULL
+static int pldm_fd_get_status(struct pldm_fd *fd,
+ const struct pldm_header_info *hdr,
+ const struct pldm_msg *req LIBPLDM_CC_UNUSED,
+ size_t req_payload_len, struct pldm_msg *resp,
+ size_t *resp_payload_len)
+{
+ int rc;
+
+ /* No request data */
+ if (req_payload_len != PLDM_GET_STATUS_REQ_BYTES) {
+ return pldm_fd_reply_cc(PLDM_ERROR_INVALID_LENGTH, hdr, resp,
+ resp_payload_len);
+ }
+
+ /* Defaults */
+ struct pldm_get_status_resp st = {
+ .current_state = fd->state,
+ .previous_state = fd->prev_state,
+ .progress_percent = PROGRESS_PERCENT_NOT_SUPPORTED,
+ };
+
+ pldm_fd_get_aux_state(fd, &st.aux_state, &st.aux_state_status);
+
+ switch (fd->state) {
+ case PLDM_FD_STATE_IDLE:
+ st.reason_code = fd->reason;
+ break;
+ case PLDM_FD_STATE_DOWNLOAD:
+ if (fd->update_comp.comp_image_size > 0) {
+ uint32_t one_percent =
+ fd->update_comp.comp_image_size / 100;
+ if (fd->update_comp.comp_image_size % 100 != 0) {
+ one_percent += 1;
+ }
+ st.progress_percent =
+ (fd->specific.download.offset / one_percent);
+ }
+ st.update_option_flags_enabled = fd->update_flags;
+ break;
+ case PLDM_FD_STATE_VERIFY:
+ st.update_option_flags_enabled = fd->update_flags;
+ st.progress_percent = fd->specific.verify.progress_percent;
+ break;
+ case PLDM_FD_STATE_APPLY:
+ st.update_option_flags_enabled = fd->update_flags;
+ st.progress_percent = fd->specific.apply.progress_percent;
+ break;
+ default:
+ break;
+ }
+
+ rc = encode_get_status_resp(hdr->instance, &st, resp, resp_payload_len);
+ if (rc) {
+ return pldm_fd_reply_errno(rc, hdr, resp, resp_payload_len);
+ }
+
+ return 0;
+}
+
+LIBPLDM_CC_NONNULL
+static int pldm_fd_cancel_update_comp(
+ struct pldm_fd *fd, const struct pldm_header_info *hdr,
+ const struct pldm_msg *req LIBPLDM_CC_UNUSED, size_t req_payload_len,
+ struct pldm_msg *resp, size_t *resp_payload_len)
+{
+ int rc;
+
+ /* No request data */
+ if (req_payload_len != PLDM_CANCEL_UPDATE_COMPONENT_REQ_BYTES) {
+ return pldm_fd_reply_cc(PLDM_ERROR_INVALID_LENGTH, hdr, resp,
+ resp_payload_len);
+ }
+
+ switch (fd->state) {
+ case PLDM_FD_STATE_DOWNLOAD:
+ case PLDM_FD_STATE_VERIFY:
+ case PLDM_FD_STATE_APPLY:
+ break;
+ default:
+ return pldm_fd_reply_cc(PLDM_FWUP_NOT_IN_UPDATE_MODE, hdr, resp,
+ resp_payload_len);
+ }
+
+ /* No response payload */
+ rc = pldm_fd_reply_cc(PLDM_SUCCESS, hdr, resp, resp_payload_len);
+ if (rc) {
+ return rc;
+ }
+
+ pldm_fd_maybe_cancel_component(fd);
+ pldm_fd_set_state(fd, PLDM_FD_STATE_READY_XFER);
+
+ return 0;
+}
+
+LIBPLDM_CC_NONNULL
+static int pldm_fd_cancel_update(struct pldm_fd *fd,
+ const struct pldm_header_info *hdr,
+ const struct pldm_msg *req LIBPLDM_CC_UNUSED,
+ size_t req_payload_len, struct pldm_msg *resp,
+ size_t *resp_payload_len)
+{
+ int rc;
+
+ /* No request data */
+ if (req_payload_len != PLDM_CANCEL_UPDATE_REQ_BYTES) {
+ return pldm_fd_reply_cc(PLDM_ERROR_INVALID_LENGTH, hdr, resp,
+ resp_payload_len);
+ }
+
+ if (fd->state == PLDM_FD_STATE_IDLE) {
+ return pldm_fd_reply_cc(PLDM_FWUP_NOT_IN_UPDATE_MODE, hdr, resp,
+ resp_payload_len);
+ }
+
+ /* Assume non_functioning_component_indication = False, in future
+ * could add a platform callback */
+ const struct pldm_cancel_update_resp resp_data = {
+ .non_functioning_component_indication = 0,
+ .non_functioning_component_bitmap = 0,
+ };
+ rc = encode_cancel_update_resp(hdr->instance, &resp_data, resp,
+ resp_payload_len);
+ if (rc) {
+ return pldm_fd_reply_errno(rc, hdr, resp, resp_payload_len);
+ }
+
+ pldm_fd_maybe_cancel_component(fd);
+ pldm_fd_set_idle(fd, PLDM_FD_CANCEL_UPDATE);
+
+ return 0;
+}
+
+LIBPLDM_CC_NONNULL
+static int pldm_fd_activate_firmware(struct pldm_fd *fd,
+ const struct pldm_header_info *hdr,
+ const struct pldm_msg *req,
+ size_t req_payload_len,
+ struct pldm_msg *resp,
+ size_t *resp_payload_len)
+{
+ uint16_t estimated_time;
+ uint8_t ccode;
+ int rc;
+ bool self_contained;
+
+ rc = decode_activate_firmware_req(req, req_payload_len,
+ &self_contained);
+ if (rc) {
+ return pldm_fd_reply_errno(rc, hdr, resp, resp_payload_len);
+ }
+
+ if (fd->state != PLDM_FD_STATE_READY_XFER) {
+ return pldm_fd_reply_cc(PLDM_FWUP_INVALID_STATE_FOR_COMMAND,
+ hdr, resp, resp_payload_len);
+ }
+
+ estimated_time = 0;
+ ccode = fd->ops->activate(fd->ops_ctx, self_contained, &estimated_time);
+
+ if (ccode == PLDM_SUCCESS ||
+ ccode == PLDM_FWUP_ACTIVATION_NOT_REQUIRED) {
+ /* Transition through states so that the prev_state is correct */
+ pldm_fd_set_state(fd, PLDM_FD_STATE_ACTIVATE);
+ pldm_fd_set_idle(fd, PLDM_FD_ACTIVATE_FW);
+ }
+
+ if (ccode == PLDM_SUCCESS) {
+ const struct pldm_activate_firmware_resp resp_data = {
+ .estimated_time_activation = estimated_time,
+ };
+ rc = encode_activate_firmware_resp(hdr->instance, &resp_data,
+ resp, resp_payload_len);
+ if (rc) {
+ return pldm_fd_reply_errno(rc, hdr, resp,
+ resp_payload_len);
+ }
+ } else {
+ return pldm_fd_reply_cc(ccode, hdr, resp, resp_payload_len);
+ }
+
+ return 0;
+}
+
+LIBPLDM_CC_NONNULL
+static uint32_t pldm_fd_fwdata_size(struct pldm_fd *fd)
+{
+ uint32_t size;
+
+ if (fd->state != PLDM_FD_STATE_DOWNLOAD) {
+ assert(false);
+ return 0;
+ }
+
+ if (fd->specific.download.offset > fd->update_comp.comp_image_size) {
+ assert(false);
+ return 0;
+ }
+ size = fd->update_comp.comp_image_size - fd->specific.download.offset;
+
+ if (size > fd->max_transfer) {
+ size = fd->max_transfer;
+ }
+ return size;
+}
+
+LIBPLDM_CC_NONNULL
+static int pldm_fd_handle_fwdata_resp(struct pldm_fd *fd,
+ const struct pldm_msg *resp,
+ size_t resp_payload_len)
+{
+ struct pldm_fd_download *dl;
+ uint32_t fwdata_size;
+ uint8_t res;
+
+ if (fd->state != PLDM_FD_STATE_DOWNLOAD) {
+ return -EPROTO;
+ }
+
+ if (fd->req.state != PLDM_FD_REQ_SENT) {
+ /* Not waiting for a response, ignore it */
+ return -EPROTO;
+ }
+
+ dl = &fd->specific.download;
+ if (fd->req.complete) {
+ /* Received data after completion */
+ return -EPROTO;
+ }
+
+ switch (resp->payload[0]) {
+ case PLDM_SUCCESS:
+ break;
+ case PLDM_FWUP_RETRY_REQUEST_FW_DATA:
+ /* Just return, let the retry timer send another request later */
+ return 0;
+ default:
+ /* Send a TransferComplete failure */
+ fd->req.state = PLDM_FD_REQ_READY;
+ fd->req.complete = true;
+ fd->req.result = PLDM_FWUP_FD_ABORTED_TRANSFER;
+ return 0;
+ }
+
+ /* Handle the received data */
+
+ fwdata_size = pldm_fd_fwdata_size(fd);
+ if (resp_payload_len != fwdata_size + 1) {
+ /* Data is incorrect size. Could indicate MCTP corruption, drop it
+ * and let retry timer handle it */
+ return -EOVERFLOW;
+ }
+
+ /* Check pldm_fd_fwdata_size calculation, should not fail */
+ if (dl->offset + fwdata_size < dl->offset ||
+ dl->offset + fwdata_size > fd->update_comp.comp_image_size) {
+ assert(false);
+ return -EINVAL;
+ }
+
+ /* Provide the data chunk to the device */
+ res = fd->ops->firmware_data(fd->ops_ctx, dl->offset, &resp->payload[1],
+ fwdata_size, &fd->update_comp);
+
+ fd->req.state = PLDM_FD_REQ_READY;
+ if (res == PLDM_FWUP_TRANSFER_SUCCESS) {
+ /* Move to next offset */
+ dl->offset += fwdata_size;
+ if (dl->offset == fd->update_comp.comp_image_size) {
+ /* Mark as complete, next progress() call will send the TransferComplete request */
+ fd->req.complete = true;
+ fd->req.result = PLDM_FWUP_TRANSFER_SUCCESS;
+ }
+ } else {
+ /* Pass the callback error as the TransferResult */
+ fd->req.complete = true;
+ fd->req.result = res;
+ }
+
+ return 0;
+}
+
+LIBPLDM_CC_NONNULL
+static int pldm_fd_handle_transfer_complete_resp(
+ struct pldm_fd *fd, const struct pldm_msg *resp LIBPLDM_CC_UNUSED,
+ size_t resp_payload_len LIBPLDM_CC_UNUSED)
+{
+ if (fd->state != PLDM_FD_STATE_DOWNLOAD) {
+ return -EPROTO;
+ }
+
+ if (fd->req.state != PLDM_FD_REQ_SENT) {
+ /* Not waiting for a response, ignore it */
+ return -EPROTO;
+ }
+
+ if (!fd->req.complete) {
+ /* Were waiting for RequestFirmwareData instead, ignore it */
+ return -EPROTO;
+ }
+
+ /* Disregard the response completion code */
+
+ /* Next state depends whether the transfer succeeded */
+ if (fd->req.result == PLDM_FWUP_TRANSFER_SUCCESS) {
+ /* Switch to Verify */
+ memset(&fd->specific, 0x0, sizeof(fd->specific));
+ fd->specific.verify.progress_percent =
+ PROGRESS_PERCENT_NOT_SUPPORTED;
+ fd->req.state = PLDM_FD_REQ_READY;
+ fd->req.complete = false;
+ pldm_fd_set_state(fd, PLDM_FD_STATE_VERIFY);
+ } else {
+ /* Wait for UA to cancel */
+ fd->req.state = PLDM_FD_REQ_FAILED;
+ }
+ return 0;
+}
+
+LIBPLDM_CC_NONNULL
+static int pldm_fd_handle_verify_complete_resp(
+ struct pldm_fd *fd, const struct pldm_msg *resp LIBPLDM_CC_UNUSED,
+ size_t resp_payload_len LIBPLDM_CC_UNUSED)
+{
+ if (fd->state != PLDM_FD_STATE_VERIFY) {
+ return -EPROTO;
+ }
+
+ if (fd->req.state != PLDM_FD_REQ_SENT) {
+ /* Not waiting for a response, ignore it */
+ return -EPROTO;
+ }
+
+ assert(fd->req.complete);
+
+ /* Disregard the response completion code */
+
+ /* Next state depends whether the verify succeeded */
+ if (fd->req.result == PLDM_FWUP_VERIFY_SUCCESS) {
+ /* Switch to Apply */
+ memset(&fd->specific, 0x0, sizeof(fd->specific));
+ fd->specific.apply.progress_percent =
+ PROGRESS_PERCENT_NOT_SUPPORTED;
+ fd->req.state = PLDM_FD_REQ_READY;
+ fd->req.complete = false;
+ pldm_fd_set_state(fd, PLDM_FD_STATE_APPLY);
+ } else {
+ /* Wait for UA to cancel */
+ fd->req.state = PLDM_FD_REQ_FAILED;
+ }
+ return 0;
+}
+
+LIBPLDM_CC_NONNULL
+static int pldm_fd_handle_apply_complete_resp(
+ struct pldm_fd *fd, const struct pldm_msg *resp LIBPLDM_CC_UNUSED,
+ size_t resp_payload_len LIBPLDM_CC_UNUSED)
+{
+ if (fd->state != PLDM_FD_STATE_APPLY) {
+ return -EPROTO;
+ }
+
+ if (fd->req.state != PLDM_FD_REQ_SENT) {
+ /* Not waiting for a response, ignore it */
+ return -EPROTO;
+ }
+
+ assert(fd->req.complete);
+
+ /* Disregard the response completion code */
+
+ /* Next state depends whether the apply succeeded */
+ if (fd->req.result == PLDM_FWUP_APPLY_SUCCESS) {
+ /* Switch to ReadyXfer */
+ fd->req.state = PLDM_FD_REQ_UNUSED;
+ pldm_fd_set_state(fd, PLDM_FD_STATE_READY_XFER);
+ } else {
+ /* Wait for UA to cancel */
+ fd->req.state = PLDM_FD_REQ_FAILED;
+ }
+ return 0;
+}
+
+LIBPLDM_CC_NONNULL
+static int pldm_fd_handle_resp(struct pldm_fd *fd, pldm_tid_t address,
+ const void *resp_msg, size_t resp_len)
+{
+ size_t resp_payload_len;
+ const struct pldm_msg *resp = resp_msg;
+
+ if (!(fd->ua_address_set && fd->ua_address == address)) {
+ // Either an early response, or a response from a wrong TID */
+ return -EBUSY;
+ }
+
+ /* Must have a ccode */
+ if (resp_len < sizeof(struct pldm_msg_hdr) + 1) {
+ return -EINVAL;
+ }
+ resp_payload_len = resp_len - sizeof(struct pldm_msg_hdr);
+
+ if (fd->req.state != PLDM_FD_REQ_SENT) {
+ // No response was expected
+ return -EPROTO;
+ }
+
+ if (fd->req.instance_id != resp->hdr.instance_id) {
+ // Response wasn't for the expected request
+ return -EPROTO;
+ }
+ if (fd->req.command != resp->hdr.command) {
+ // Response wasn't for the expected request
+ return -EPROTO;
+ }
+
+ fd->update_timestamp_fd_t1 = pldm_fd_now(fd);
+
+ switch (resp->hdr.command) {
+ case PLDM_REQUEST_FIRMWARE_DATA:
+ return pldm_fd_handle_fwdata_resp(fd, resp, resp_payload_len);
+ break;
+ case PLDM_TRANSFER_COMPLETE:
+ return pldm_fd_handle_transfer_complete_resp(fd, resp,
+ resp_payload_len);
+ break;
+ case PLDM_VERIFY_COMPLETE:
+ return pldm_fd_handle_verify_complete_resp(fd, resp,
+ resp_payload_len);
+ break;
+ case PLDM_APPLY_COMPLETE:
+ return pldm_fd_handle_apply_complete_resp(fd, resp,
+ resp_payload_len);
+ break;
+ default:
+ /* Unsolicited response. Already compared to command above */
+ assert(false);
+ return -EINVAL;
+ }
+}
+
+LIBPLDM_CC_NONNULL
+static int pldm_fd_progress_download(struct pldm_fd *fd, struct pldm_msg *req,
+ size_t *req_payload_len)
+{
+ uint8_t instance_id;
+ struct pldm_fd_download *dl;
+ int rc;
+
+ if (!pldm_fd_req_should_send(fd)) {
+ /* Nothing to do */
+ *req_payload_len = 0;
+ return 0;
+ }
+
+ instance_id = pldm_fd_req_next_instance(&fd->req);
+ dl = &fd->specific.download;
+ if (fd->req.complete) {
+ /* Send TransferComplete */
+ rc = encode_transfer_complete_req(instance_id, fd->req.result,
+ req, req_payload_len);
+ } else {
+ /* Send a new RequestFirmwareData */
+ const struct pldm_request_firmware_data_req req_params = {
+ .offset = dl->offset,
+ .length = pldm_fd_fwdata_size(fd),
+ };
+
+ rc = encode_request_firmware_data_req(instance_id, &req_params,
+ req, req_payload_len);
+ }
+
+ if (rc) {
+ return rc;
+ }
+
+ /* Wait for response */
+ fd->req.state = PLDM_FD_REQ_SENT;
+ fd->req.instance_id = req->hdr.instance_id;
+ fd->req.command = req->hdr.command;
+ fd->req.sent_time = pldm_fd_now(fd);
+
+ return 0;
+}
+
+LIBPLDM_CC_NONNULL
+static int pldm_fd_progress_verify(struct pldm_fd *fd, struct pldm_msg *req,
+ size_t *req_payload_len)
+{
+ uint8_t instance_id;
+ int rc;
+
+ if (!pldm_fd_req_should_send(fd)) {
+ /* Nothing to do */
+ *req_payload_len = 0;
+ return 0;
+ }
+
+ if (!fd->req.complete) {
+ bool pending = false;
+ uint8_t res;
+ res = fd->ops->verify(fd->ops_ctx, &fd->update_comp, &pending,
+ &fd->specific.verify.progress_percent);
+ if (pending) {
+ if (res == PLDM_FWUP_VERIFY_SUCCESS) {
+ /* Return without a VerifyComplete request.
+ * Will call verify() again on next call */
+ *req_payload_len = 0;
+ return 0;
+ }
+ /* This is an API infraction by the implementer, return a distinctive failure */
+ res = PLDM_FWUP_VENDOR_VERIFY_RESULT_RANGE_MAX;
+ }
+ fd->req.result = res;
+ fd->req.complete = true;
+ }
+
+ instance_id = pldm_fd_req_next_instance(&fd->req);
+ rc = encode_verify_complete_req(instance_id, fd->req.result, req,
+ req_payload_len);
+ if (rc) {
+ return rc;
+ }
+
+ /* Wait for response */
+ fd->req.state = PLDM_FD_REQ_SENT;
+ fd->req.instance_id = req->hdr.instance_id;
+ fd->req.command = req->hdr.command;
+ fd->req.sent_time = pldm_fd_now(fd);
+
+ return 0;
+}
+
+LIBPLDM_CC_NONNULL
+static int pldm_fd_progress_apply(struct pldm_fd *fd, struct pldm_msg *req,
+ size_t *req_payload_len)
+{
+ uint8_t instance_id;
+ int rc;
+
+ if (!pldm_fd_req_should_send(fd)) {
+ /* Nothing to do */
+ *req_payload_len = 0;
+ return 0;
+ }
+
+ if (!fd->req.complete) {
+ bool pending = false;
+ uint8_t res;
+ res = fd->ops->apply(fd->ops_ctx, &fd->update_comp, &pending,
+ &fd->specific.apply.progress_percent);
+ if (pending) {
+ if (res == PLDM_FWUP_APPLY_SUCCESS) {
+ /* Return without a ApplyComplete request.
+ * Will call apply() again on next call */
+ *req_payload_len = 0;
+ return 0;
+ }
+ /* This is an API infraction by the implementer, return a distinctive failure */
+ res = PLDM_FWUP_VENDOR_APPLY_RESULT_RANGE_MAX;
+ }
+ fd->req.result = res;
+ fd->req.complete = true;
+ if (fd->req.result ==
+ PLDM_FWUP_APPLY_SUCCESS_WITH_ACTIVATION_METHOD) {
+ /* modified activation method isn't currently handled */
+ fd->req.result = PLDM_FWUP_APPLY_SUCCESS;
+ }
+ }
+
+ instance_id = pldm_fd_req_next_instance(&fd->req);
+ const struct pldm_apply_complete_req req_data = {
+ .apply_result = fd->req.result,
+ .comp_activation_methods_modification = { 0 },
+ };
+ rc = encode_apply_complete_req(instance_id, &req_data, req,
+ req_payload_len);
+ if (rc) {
+ return rc;
+ }
+
+ /* Wait for response */
+ fd->req.state = PLDM_FD_REQ_SENT;
+ fd->req.instance_id = req->hdr.instance_id;
+ fd->req.command = req->hdr.command;
+ fd->req.sent_time = pldm_fd_now(fd);
+
+ return 0;
+}
+
+LIBPLDM_ABI_TESTING
+struct pldm_fd *pldm_fd_new(const struct pldm_fd_ops *ops, void *ops_ctx)
+{
+ struct pldm_fd *fd = malloc(sizeof(*fd));
+ if (fd) {
+ if (pldm_fd_setup(fd, sizeof(*fd), ops, ops_ctx) == 0) {
+ return fd;
+ }
+ free(fd);
+ fd = NULL;
+ }
+ return fd;
+}
+
+LIBPLDM_ABI_TESTING
+int pldm_fd_setup(struct pldm_fd *fd, size_t pldm_fd_size,
+ const struct pldm_fd_ops *ops, void *ops_ctx)
+{
+ if (fd == NULL || ops == NULL) {
+ return -EINVAL;
+ }
+
+ if (ops->device_identifiers == NULL || ops->components == NULL ||
+ ops->imageset_versions == NULL || ops->update_component == NULL ||
+ ops->transfer_size == NULL || ops->firmware_data == NULL ||
+ ops->verify == NULL || ops->activate == NULL ||
+ ops->cancel_update_component == NULL || ops->now == NULL) {
+ return -EINVAL;
+ }
+
+ if (pldm_fd_size < sizeof(struct pldm_fd)) {
+ /* Safety check that sufficient storage was provided for *fd,
+ * in case PLDM_SIZEOF_PLDM_FD is incorrect */
+ return -EINVAL;
+ }
+ memset(fd, 0x0, sizeof(*fd));
+ fd->ops = ops;
+ fd->ops_ctx = ops_ctx;
+ fd->fd_t1_timeout = DEFAULT_FD_T1_TIMEOUT;
+ fd->fd_t2_retry_time = DEFAULT_FD_T2_RETRY_TIME;
+
+ return 0;
+}
+
+LIBPLDM_ABI_TESTING
+int pldm_fd_handle_msg(struct pldm_fd *fd, pldm_tid_t remote_address,
+ const void *in_msg, size_t in_len, void *out_msg,
+ size_t *out_len)
+{
+ size_t req_payload_len;
+ size_t resp_payload_len;
+ struct pldm_header_info hdr;
+ const struct pldm_msg *req = in_msg;
+ struct pldm_msg *resp = out_msg;
+ uint8_t rc;
+
+ if (fd == NULL || in_msg == NULL || out_msg == NULL ||
+ out_len == NULL) {
+ return -EINVAL;
+ }
+
+ if (in_len < sizeof(struct pldm_msg_hdr)) {
+ return -EOVERFLOW;
+ }
+ req_payload_len = in_len - sizeof(struct pldm_msg_hdr);
+
+ rc = unpack_pldm_header(&req->hdr, &hdr);
+ if (rc != PLDM_SUCCESS) {
+ return -EINVAL;
+ }
+
+ if (hdr.pldm_type != PLDM_FWUP) {
+ /* Caller should not have passed non-pldmfw */
+ return -ENOMSG;
+ }
+
+ if (hdr.msg_type == PLDM_RESPONSE) {
+ *out_len = 0;
+ return pldm_fd_handle_resp(fd, remote_address, in_msg, in_len);
+ }
+
+ if (hdr.msg_type != PLDM_REQUEST) {
+ return -EPROTO;
+ }
+
+ /* Space for header plus completion code */
+ if (*out_len < sizeof(struct pldm_msg_hdr) + 1) {
+ return -EOVERFLOW;
+ }
+ resp_payload_len = *out_len - sizeof(struct pldm_msg_hdr);
+
+ /* Check address */
+ switch (hdr.command) {
+ /* Information or cancel commands are always allowed */
+ case PLDM_QUERY_DEVICE_IDENTIFIERS:
+ case PLDM_GET_FIRMWARE_PARAMETERS:
+ case PLDM_GET_STATUS:
+ case PLDM_CANCEL_UPDATE:
+ case PLDM_QUERY_DOWNSTREAM_DEVICES:
+ case PLDM_QUERY_DOWNSTREAM_IDENTIFIERS:
+ case PLDM_QUERY_DOWNSTREAM_FIRMWARE_PARAMETERS:
+ /* Request Update handler will set address */
+ case PLDM_REQUEST_UPDATE:
+ break;
+ default:
+ /* Requests must come from the same address that requested the update */
+ if (!fd->ua_address_set || remote_address != fd->ua_address) {
+ return pldm_fd_reply_cc(PLDM_ERROR_NOT_READY, &hdr,
+ resp, &resp_payload_len);
+ }
+ }
+
+ /* Update timeout */
+ switch (hdr.command) {
+ case PLDM_REQUEST_UPDATE:
+ case PLDM_PASS_COMPONENT_TABLE:
+ case PLDM_UPDATE_COMPONENT:
+ case PLDM_CANCEL_UPDATE:
+ fd->update_timestamp_fd_t1 = pldm_fd_now(fd);
+ break;
+ default:
+ break;
+ }
+
+ /* Dispatch command */
+ switch (hdr.command) {
+ case PLDM_QUERY_DEVICE_IDENTIFIERS:
+ rc = pldm_fd_qdi(fd, &hdr, req, req_payload_len, resp,
+ &resp_payload_len);
+ break;
+ case PLDM_GET_FIRMWARE_PARAMETERS:
+ rc = pldm_fd_fw_param(fd, &hdr, req, req_payload_len, resp,
+ &resp_payload_len);
+ break;
+ case PLDM_REQUEST_UPDATE:
+ rc = pldm_fd_request_update(fd, &hdr, req, req_payload_len,
+ resp, &resp_payload_len,
+ remote_address);
+ break;
+ case PLDM_PASS_COMPONENT_TABLE:
+ rc = pldm_fd_pass_comp(fd, &hdr, req, req_payload_len, resp,
+ &resp_payload_len);
+ break;
+ case PLDM_UPDATE_COMPONENT:
+ rc = pldm_fd_update_comp(fd, &hdr, req, req_payload_len, resp,
+ &resp_payload_len);
+ break;
+ case PLDM_GET_STATUS:
+ rc = pldm_fd_get_status(fd, &hdr, req, req_payload_len, resp,
+ &resp_payload_len);
+ break;
+ case PLDM_CANCEL_UPDATE_COMPONENT:
+ rc = pldm_fd_cancel_update_comp(fd, &hdr, req, req_payload_len,
+ resp, &resp_payload_len);
+ break;
+ case PLDM_CANCEL_UPDATE:
+ rc = pldm_fd_cancel_update(fd, &hdr, req, req_payload_len, resp,
+ &resp_payload_len);
+ break;
+ case PLDM_ACTIVATE_FIRMWARE:
+ rc = pldm_fd_activate_firmware(fd, &hdr, req, req_payload_len,
+ resp, &resp_payload_len);
+ break;
+ default:
+ rc = pldm_fd_reply_cc(PLDM_ERROR_UNSUPPORTED_PLDM_CMD, &hdr,
+ resp, &resp_payload_len);
+ }
+
+ if (rc == 0) {
+ *out_len = resp_payload_len + sizeof(struct pldm_msg_hdr);
+ }
+
+ return rc;
+}
+
+LIBPLDM_ABI_TESTING
+int pldm_fd_progress(struct pldm_fd *fd, void *out_msg, size_t *out_len,
+ pldm_tid_t *address)
+{
+ size_t req_payload_len;
+ struct pldm_msg *req = out_msg;
+ int rc = -EINVAL;
+
+ if (fd == NULL || out_msg == NULL || out_len == NULL) {
+ return -EINVAL;
+ }
+
+ /* Space for header */
+ if (*out_len < sizeof(struct pldm_msg_hdr)) {
+ return -EOVERFLOW;
+ }
+ req_payload_len = *out_len - sizeof(struct pldm_msg_hdr);
+ *out_len = 0;
+
+ switch (fd->state) {
+ case PLDM_FD_STATE_DOWNLOAD:
+ rc = pldm_fd_progress_download(fd, req, &req_payload_len);
+ break;
+ case PLDM_FD_STATE_VERIFY:
+ rc = pldm_fd_progress_verify(fd, req, &req_payload_len);
+ break;
+ case PLDM_FD_STATE_APPLY:
+ rc = pldm_fd_progress_apply(fd, req, &req_payload_len);
+ break;
+ case PLDM_FD_STATE_IDLE:
+ req_payload_len = 0;
+ break;
+ default:
+ req_payload_len = 0;
+ // Other Update Mode states have a timeout
+ if ((pldm_fd_now(fd) - fd->update_timestamp_fd_t1) >
+ fd->fd_t1_timeout) {
+ pldm_fd_maybe_cancel_component(fd);
+ pldm_fd_idle_timeout(fd);
+ }
+ break;
+ }
+
+ if (rc == 0 && fd->ua_address_set && req_payload_len > 0) {
+ *out_len = req_payload_len + sizeof(struct pldm_msg_hdr);
+ *address = fd->ua_address;
+ }
+
+ return rc;
+}
+
+LIBPLDM_ABI_TESTING
+int pldm_fd_set_update_idle_timeout(struct pldm_fd *fd, uint32_t time)
+{
+ if (fd == NULL) {
+ return -EINVAL;
+ }
+
+ fd->fd_t1_timeout = time;
+ return 0;
+}
+
+LIBPLDM_ABI_TESTING
+int pldm_fd_set_request_retry_time(struct pldm_fd *fd, uint32_t time)
+{
+ if (fd == NULL) {
+ return -EINVAL;
+ }
+
+ fd->fd_t2_retry_time = time;
+ return 0;
+}