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/CHANGELOG.md b/CHANGELOG.md
index 0e04e0e..adcbb06 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -65,6 +65,8 @@
9. Add Firmware Device side firmware_update encode/decode functions
+10. Add firmware update FD responder
+
### Fixed
1. dsp: platform: Fix location of closing paren in overflow detection
diff --git a/include/libpldm/firmware_fd.h b/include/libpldm/firmware_fd.h
new file mode 100644
index 0000000..5c45390
--- /dev/null
+++ b/include/libpldm/firmware_fd.h
@@ -0,0 +1,341 @@
+/* SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later */
+#pragma once
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdint.h>
+#include <stdbool.h>
+
+#include <libpldm/pldm.h>
+#include <libpldm/base.h>
+#include <libpldm/utils.h>
+#include <libpldm/firmware_update.h>
+
+/** @struct pldm_firmware_component_standalone
+ *
+ * A PLDM Firmware Update Component representation, for use
+ * with pldm_fd_ops callbacks.
+*/
+struct pldm_firmware_component_standalone {
+ uint16_t comp_classification;
+ uint16_t comp_identifier;
+ uint8_t comp_classification_index;
+
+ struct pldm_firmware_version active_ver;
+ struct pldm_firmware_version pending_ver;
+
+ bitfield16_t comp_activation_methods;
+ bitfield32_t capabilities_during_update;
+};
+
+/** @struct pldm_firmware_update_component
+ *
+ * An entry for Pass Component Table or Update Component
+*/
+struct pldm_firmware_update_component {
+ uint16_t comp_classification;
+ uint16_t comp_identifier;
+ uint8_t comp_classification_index;
+ uint32_t comp_comparison_stamp;
+ struct pldm_firmware_string version;
+
+ /* Not set for PassComponentTable */
+ uint32_t comp_image_size;
+ /* Not set for PassComponentTable */
+ bitfield32_t update_option_flags;
+};
+
+/** @struct pldm_fd_ops
+ *
+ * Device-specific callbacks provided by an application,
+ * to define the device update behaviour.
+ *
+ * These will be called by the FD responder when pldm_fd_handle_msg()
+ * or pldm_fd_progress() are called by the application.
+ *
+ * Note that return values vary between functions. Some return a PLDM
+ * completion code or status code which will be sent to the UA, others
+ * return a negative errno on failure.
+*/
+struct pldm_fd_ops {
+ /** @brief Provide PLDM descriptors
+ *
+ * @param[in] ctx - callback context
+ * @param[out] ret_descriptors_count - count of descriptors returned
+ * @param[out] ret_descriptors - array of descriptors.
+ *
+ * @return 0 on success, a negative errno value on failure.
+ * (specific errno value is ignored)
+ */
+ int (*device_identifiers)(
+ void *ctx, uint8_t *ret_descriptors_count,
+ const struct pldm_descriptor **ret_descriptors);
+
+ /** @brief Provide PLDM component table from the application
+ *
+ * @param[in] ctx - callback context
+ * @param[out] ret_entry_count - length of returned ret_entries
+ * @param[out] ret_entries - an array of component pointers
+ *
+ * @return 0 on success, a negative errno value on failure.
+ * (specific errno value is ignored)
+ *
+ * It will be called several times in an update flow.
+ */
+ int (*components)(
+ void *ctx, uint16_t *ret_entry_count,
+ const struct pldm_firmware_component_standalone ***ret_entries);
+
+ /** @brief Return imageset version from the application
+ *
+ * @param[in] ctx - callback context
+ * @param[out] ret_active - ActiveComponentVersion string
+ * @param[out] ret_active - PendingComponentVersion string
+ *
+ * @return 0 on success, a negative errno value on failure.
+ * (specific errno value is ignored)
+ *
+ * This is used by the FD responder for GetFirmwareParameters.
+ */
+ int (*imageset_versions)(void *ctx,
+ struct pldm_firmware_string *ret_active,
+ struct pldm_firmware_string *ret_pending);
+
+ /** @brief Called on PassComponentTable or UpdateComponent
+ *
+ * @param[in] ctx - callback context
+ * @param[in] update - will be set for UpdateComponent, and indicates that
+ * an update flow is starting, with the same comp used
+ * for subsequent firmware_data, verify, apply callbacks.
+ * @param[in] comp - the component being used. The FD implementation
+ * will only pass comp that has already been
+ * validated against the pldm_fd_ops.components callback.
+ *
+ * @return PLDM_CRC_COMP_CAN_BE_UPDATED if the component can be updated.
+ */
+ enum pldm_component_response_codes (*update_component)(
+ void *ctx, bool update,
+ const struct pldm_firmware_update_component *comp);
+
+ /** @brief Provide the transfer size to use
+ *
+ * @param[in] ctx - callback context
+ * @param[in] ua_max_transfer_size - size requested by the UA.
+ *
+ * @return The transfer size to use. This will be clamped to
+ * 32 <= size <= ua_max_transfer_size.
+ * The final data chunk may be shorter.
+ */
+ uint32_t (*transfer_size)(void *ctx, uint32_t ua_max_transfer_size);
+
+ /* @brief Provides firmware update data from the UA
+ *
+ * @param[in] ctx - callback context
+ * @param[in] offset - offset of the data
+ * @param[in] data - firmware data buffer
+ * @param[in] len - length of data
+ * @param[in] comp - the relevant component
+ *
+ * @return TransferComplete code - either
+ * enum pldm_firmware_update_common_error_codes or
+ * enum pldm_firmware_update_transfer_result_values.
+ *
+ * PLDM_FWUP_TRANSFER_SUCCESS will accept the data chunk, other codes will
+ * abort the transfer, returning that code as TransferComplete
+ */
+ uint8_t (*firmware_data)(
+ void *ctx, uint32_t offset, const uint8_t *data, uint32_t len,
+ const struct pldm_firmware_update_component *comp);
+
+ /* @brief Requests the application verify the update
+ *
+ * @param[in] ctx - callback context
+ * @param[in] comp - the relevant component
+ * @param[out] ret_pending - set when verify will run asynchronously
+ * @param[out] ret_progress_percent - can optionally be set
+ * during asynchronous verify,
+ * or leave defaulted (101).
+ *
+ * @return VerifyComplete code - either
+ * enum pldm_firmware_update_common_error_codes or
+ * enum pldm_firmware_update_verify_result_values.
+ *
+ * verify() will only be called once all firmware_data (up to the UA-specified
+ * comp_image_size) has been provided. Implementations should check that length
+ * as part of verification, if not already checked.
+ *
+ * If the verify is going to complete asynchronously, implementations set
+ * *ret_pending=true and return PLDM_FWUP_VERIFY_SUCCESS. The FD will then
+ * call verify() again when pldm_fd_progress() is called.
+ */
+ uint8_t (*verify)(void *ctx,
+ const struct pldm_firmware_update_component *comp,
+ bool *ret_pending, uint8_t *ret_progress_percent);
+
+ /* @brief Requests the application apply the update
+ *
+ * @param[in] ctx - callback context
+ * @param[in] comp - the relevant component
+ * @param[out] ret_pending - set when apply will run asynchronously
+ * @param[out] ret_progress_percent - can optionally be set
+ * during asynchronous apply,
+ * or leave defaulted (101).
+ *
+ * @return ApplyComplete code - either
+ * enum pldm_firmware_update_common_error_codes or
+ * enum pldm_firmware_update_apply_result_values.
+ *
+ * If the apply is going to complete asynchronously, implementations set
+ * *ret_pending=true and return PLDM_FWUP_APPLY_SUCCESS. The FD will then
+ * call apply() again when pldm_fd_progress() is called.
+ */
+ uint8_t (*apply)(void *ctx,
+ const struct pldm_firmware_update_component *comp,
+ bool *ret_pending, uint8_t *ret_progress_percent);
+
+ /* @brief Activates new firmware
+ *
+ * @param[in] ctx - callback context
+ * @param[in] self_contained - Self Contained Activation is requested
+ * @param[out] ret_estimated_time - a time in seconds to perform
+ * self activation, or may be left as 0.
+ *
+ * @return PLDM completion code
+ *
+ * The device implementation is responsible for checking that
+ * expected components have been updated, returning
+ * PLDM_FWUP_INCOMPLETE_UPDATE if not.
+ */
+ uint8_t (*activate)(void *ctx, bool self_contained,
+ uint16_t *ret_estimated_time);
+
+ /* @brief Cancel Update Component
+ *
+ * @param[in] ctx - callback context
+ * @param[in] comp - the relevant component
+ *
+ * Called when a component update is cancelled prior to being applied.
+ * This function is called for both Cancel Update Component
+ * and Cancel Update (when a component is currently in progress). */
+ void (*cancel_update_component)(
+ void *ctx, const struct pldm_firmware_update_component *comp);
+
+ /* @brief Returns a monotonic timestamp
+ *
+ * @param[in] ctx - callback context
+ *
+ * @return timestamp in milliseconds, from an arbitrary origin.
+ Must not go backwards.
+ */
+ uint64_t (*now)(void *ctx);
+};
+
+/* Static storage can be allocated with
+ * PLDM_SIZEOF_PLDM_FD macro */
+#define PLDM_ALIGNOF_PLDM_FD 8
+struct pldm_fd;
+
+/** @brief Allocate and initialise a FD responder
+ *
+ * @param[in] ops - Application provided callbacks which define the device
+ * update behaviour
+ * @param[in] ops_ctx - opaque context pointer that will be passed as ctx
+ * to ops callbacks
+ *
+ * @return a malloced struct pldm_fd, owned by the caller. It should be released
+ * with free(). Returns NULL on failure.
+ *
+ * This will call pldm_fd_setup() on the allocated pldm_fd.
+ */
+struct pldm_fd *pldm_fd_new(const struct pldm_fd_ops *ops, void *ops_ctx);
+
+/** @brief Initialise a FD responder struct
+ *
+ * @param[in] fd - A pointer to a struct pldm_fd. Applications can allocate this
+ * in static storage of size PLDM_SIZEOF_PLDM_FD if required.
+ * @param[in] pldm_fd_size - applications should pass PLDM_SIZEOF_PLDM_FD, to check
+ * for consistency with the fd pointer.
+ * @param[in] ops - Application provided callbacks which define the device
+ * update behaviour
+ * @param[in] ops_ctx - opaque context pointer that will be passed as ctx
+ * to ops callbacks
+ *
+ * @return 0 on success, a negative errno value on failure.
+ */
+int pldm_fd_setup(struct pldm_fd *fd, size_t pldm_fd_size,
+ const struct pldm_fd_ops *ops, void *ops_ctx);
+
+/** @brief Handle a PLDM Firmware Update message
+ *
+ * @param[in] fd
+ * @param[in] remote_address - the source address of the message.
+ * @param[in] in_msg - PLDM incoming message payload
+ * @param[in] in_len - length of in_msg buffer
+ * @param[out] out_msg - PLDM outgoing message payload buffer
+ * @param[inout] out_len - length of available out_msg buffer, will be updated
+ * with the length written to out_msg.
+ *
+ * @return 0 on success, a negative errno value on failure.
+ *
+ * Will return a message to send if out_len > 0
+ * and returning 0.
+ */
+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);
+
+/** @brief Handle periodic progress events
+ *
+ * @param[in] fd
+ * @param[out] out_msg - PLDM outgoing message payload buffer
+ * @param[inout] out_len - length of available out_msg buffer, will be updated
+ * with the length written to out_msg.
+ * @param[out] remote_address - destination address for the message to send.
+ * This is the address used to initiate the update,
+ * from a previous pldm_fd_handle_msg call.
+ *
+ * @return 0 on success, a negative errno value on failure.
+ *
+ * Will return a message to send to remote_address if out_len > 0
+ * and returning 0.
+ *
+ * This could be called periodically by the application to send retries
+ * during an update flow. A 1 second interval is recommended.
+ */
+int pldm_fd_progress(struct pldm_fd *fd, void *out_msg, size_t *out_len,
+ pldm_tid_t *remote_address);
+
+/** @brief Set update mode idle timeout
+ *
+ * @param[in] fd
+ * @param[in] time - Amount of time before the FD shall exit from update mode
+ * if no command is received, in milliseconds. FD_T1.
+ * Should be 60000-120000 (60-120 sec), initial default is
+ * 120000.
+ *
+ * @return 0 on success, a negative errno value on failure.
+ */
+int pldm_fd_set_update_idle_timeout(struct pldm_fd *fd, uint32_t time);
+
+/** @brief Set request retry time
+ *
+ * @param[in] fd
+ * @param[in] time - Time for retries of Request Firmware Data,
+ * Verify, Apply commands, in miliseconds. FD_T2.
+ * Should be 1000-5000, initial default is 1000.
+ *
+ * Will return a message to send to remote_address if out_len > 0
+ * and returning 0.
+ *
+ * This could be called periodically by the application to send retries
+ * during an update flow. A 1 second interval is recommended.
+ *
+ * @return 0 on success, a negative errno value on failure.
+ */
+int pldm_fd_set_request_retry_time(struct pldm_fd *fd, uint32_t time);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/include/libpldm/meson.build b/include/libpldm/meson.build
index 9dff827..a95cb56 100644
--- a/include/libpldm/meson.build
+++ b/include/libpldm/meson.build
@@ -5,6 +5,7 @@
'bios_table.h',
'compiler.h',
'entity.h',
+ 'firmware_fd.h',
'firmware_update.h',
'fru.h',
'instance-id.h',
@@ -37,3 +38,19 @@
install_headers(libpldm_headers, subdir: 'libpldm', preserve_path: true)
+
+# TODO: these should depend on the input headers so they rebuild
+# on changes, unclear how to do that.
+include_src = include_directories('../../src', is_system: true)
+sizeof_pldm_fd = compiler.sizeof(
+ 'struct pldm_fd',
+ prefix: '#include "firmware_device/fd-internal.h"',
+ include_directories: [include_src, libpldm_include_dir],
+)
+sizes_h = configure_file(
+ configuration: {'sizeof_pldm_fd': sizeof_pldm_fd},
+ input: 'sizes.h.in',
+ output: 'sizes.h',
+ install: true,
+ install_dir: 'libpldm',
+)
diff --git a/include/libpldm/sizes.h.in b/include/libpldm/sizes.h.in
new file mode 100644
index 0000000..85c0330
--- /dev/null
+++ b/include/libpldm/sizes.h.in
@@ -0,0 +1,4 @@
+#pragma once
+
+/* sizeof(struct pldm_fd) */
+#define PLDM_SIZEOF_PLDM_FD @sizeof_pldm_fd@
diff --git a/src/firmware_device/fd-internal.h b/src/firmware_device/fd-internal.h
new file mode 100644
index 0000000..3873c80
--- /dev/null
+++ b/src/firmware_device/fd-internal.h
@@ -0,0 +1,90 @@
+/* SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later */
+#pragma once
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <string.h>
+
+#include <libpldm/pldm.h>
+#include <libpldm/firmware_update.h>
+#include <libpldm/firmware_fd.h>
+#include <libpldm/utils.h>
+
+typedef uint64_t pldm_fd_time_t;
+
+struct pldm_fd_req {
+ enum pldm_fd_req_state {
+ // pldm_fd_req instance is unused
+ PLDM_FD_REQ_UNUSED = 0,
+ // Ready to send a request
+ PLDM_FD_REQ_READY,
+ // Waiting for a response
+ PLDM_FD_REQ_SENT,
+ // Completed and failed, will not send more requests.
+ // Waiting for a cancel from the UA.
+ PLDM_FD_REQ_FAILED,
+ } state;
+
+ /* Set once when ready to move to next state, will return
+ * this result for TransferComplete/VerifyComplete/ApplyComplete request. */
+ bool complete;
+ /* Only valid when complete is set */
+ uint8_t result;
+
+ /* Only valid in SENT state */
+ uint8_t instance_id;
+ uint8_t command;
+ pldm_fd_time_t sent_time;
+};
+
+struct pldm_fd_download {
+ uint32_t offset;
+};
+
+struct pldm_fd_verify {
+ uint8_t progress_percent;
+};
+
+struct pldm_fd_apply {
+ uint8_t progress_percent;
+};
+
+struct pldm_fd {
+ enum pldm_firmware_device_states state;
+ enum pldm_firmware_device_states prev_state;
+
+ /* Reason for last transition to idle state,
+ * only valid when state == PLDM_FD_STATE_IDLE */
+ enum pldm_get_status_reason_code_values reason;
+
+ /* State-specific content */
+ union {
+ struct pldm_fd_download download;
+ struct pldm_fd_verify verify;
+ struct pldm_fd_apply apply;
+ } specific;
+ /* Details of the component currently being updated.
+ * Set by UpdateComponent, available during download/verify/apply.
+ * Also used as temporary storage for PassComponentTable */
+ struct pldm_firmware_update_component update_comp;
+ bitfield32_t update_flags;
+
+ /* Used for download/verify/apply requests */
+ struct pldm_fd_req req;
+
+ /* Address of the UA */
+ pldm_tid_t ua_address;
+ bool ua_address_set;
+
+ /* Maximum size allowed by the UA or platform implementation */
+ uint32_t max_transfer;
+
+ /* Timestamp for FD T1 timeout, milliseconds */
+ pldm_fd_time_t update_timestamp_fd_t1;
+
+ pldm_fd_time_t fd_t1_timeout;
+ pldm_fd_time_t fd_t2_retry_time;
+
+ const struct pldm_fd_ops *ops;
+ void *ops_ctx;
+};
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;
+}
diff --git a/src/firmware_device/meson.build b/src/firmware_device/meson.build
new file mode 100644
index 0000000..3e21bda
--- /dev/null
+++ b/src/firmware_device/meson.build
@@ -0,0 +1 @@
+libpldm_sources += files('fd.c')
diff --git a/src/meson.build b/src/meson.build
index a8e54e7..3c8afc1 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -15,6 +15,8 @@
subdir('oem/meta')
endif
+subdir('firmware_device')
+
libpldm_link_args = []
foreach alias : libpldm_deprecated_aliases
libpldm_link_args += '-Wl,--defsym=@0@=@1@'.format(alias[0], alias[1])