fw-update: Add support for canceling component updates
This commit implements the CancelUpdateComponent command functionality
to handle component update failures during firmware updates.
This change improves firmware update reliability by gracefully handling
component update failures and allowing partial updates to proceed when
possible by sending ActivateFirmware command if even one of the
component updates succeed.
Tests:
- Triggered a verification failure with a corrupted package. The
relevant logs are seen in the journal
```
pldmd[24962]: Failed to verify component endpoint ID '16' and version
'cec1736Ecfw-01.04.0022.0000' with transfer result - '149'
pldmd[24962]: Sending cancel update component request for endpoint ID 16
pldmd[24962]: Received cancel update component response from endpoint ID
16
```
Change-Id: I7618045f3d3df3fc4f64c493fc39f2a0f7898b95
Signed-off-by: P Arun Kumar Reddy <arunpapannagari23@gmail.com>
diff --git a/fw-update/device_updater.cpp b/fw-update/device_updater.cpp
index 77412b9..a879b23 100644
--- a/fw-update/device_updater.cpp
+++ b/fw-update/device_updater.cpp
@@ -504,6 +504,8 @@
"Failure in transfer of the component endpoint ID '{EID}' and version '{COMPONENT_VERSION}' with transfer result - {RESULT}",
"EID", eid, "COMPONENT_VERSION", compVersion, "RESULT",
transferResult);
+ componentUpdateStatus[componentIndex] = false;
+ sendCancelUpdateComponentRequest();
}
rc = encode_transfer_complete_resp(request->hdr.instance_id, completionCode,
@@ -562,6 +564,8 @@
"Failed to verify component endpoint ID '{EID}' and version '{COMPONENT_VERSION}' with transfer result - '{RESULT}'",
"EID", eid, "COMPONENT_VERSION", compVersion, "RESULT",
verifyResult);
+ componentUpdateStatus[componentIndex] = false;
+ sendCancelUpdateComponentRequest();
}
rc = encode_verify_complete_resp(request->hdr.instance_id, completionCode,
@@ -617,12 +621,32 @@
"Component endpoint ID '{EID}' with '{COMPONENT_VERSION}' apply complete.",
"EID", eid, "COMPONENT_VERSION", compVersion);
updateManager->updateActivationProgress();
+ if (componentIndex == applicableComponents.size() - 1)
+ {
+ componentIndex = 0;
+ componentUpdateStatus.clear();
+ componentUpdateStatus[componentIndex] = true;
+ pldmRequest = std::make_unique<sdeventplus::source::Defer>(
+ updateManager->event,
+ std::bind(&DeviceUpdater::sendActivateFirmwareRequest, this));
+ }
+ else
+ {
+ componentIndex++;
+ componentUpdateStatus[componentIndex] = true;
+ pldmRequest = std::make_unique<sdeventplus::source::Defer>(
+ updateManager->event,
+ std::bind(&DeviceUpdater::sendUpdateComponentRequest, this,
+ componentIndex));
+ }
}
else
{
error(
"Failed to apply component endpoint ID '{EID}' and version '{COMPONENT_VERSION}', error - {ERROR}",
"EID", eid, "COMPONENT_VERSION", compVersion, "ERROR", applyResult);
+ componentUpdateStatus[componentIndex] = false;
+ sendCancelUpdateComponentRequest();
}
rc = encode_apply_complete_resp(request->hdr.instance_id, completionCode,
@@ -635,22 +659,6 @@
return response;
}
- if (componentIndex == applicableComponents.size() - 1)
- {
- componentIndex = 0;
- pldmRequest = std::make_unique<sdeventplus::source::Defer>(
- updateManager->event,
- std::bind(&DeviceUpdater::sendActivateFirmwareRequest, this));
- }
- else
- {
- componentIndex++;
- pldmRequest = std::make_unique<sdeventplus::source::Defer>(
- updateManager->event,
- std::bind(&DeviceUpdater::sendUpdateComponentRequest, this,
- componentIndex));
- }
-
return response;
}
@@ -723,6 +731,105 @@
updateManager->updateDeviceCompletion(eid, true);
}
+void DeviceUpdater::sendCancelUpdateComponentRequest()
+{
+ pldmRequest.reset();
+ auto instanceId = updateManager->instanceIdDb.next(eid);
+ Request request(sizeof(pldm_msg_hdr));
+ auto requestMsg = new (request.data()) pldm_msg;
+
+ auto rc = encode_cancel_update_component_req(
+ instanceId, requestMsg, PLDM_CANCEL_UPDATE_COMPONENT_REQ_BYTES);
+ if (rc)
+ {
+ updateManager->instanceIdDb.free(eid, instanceId);
+ error(
+ "Failed to encode cancel update component request for endpoint ID '{EID}', component index '{COMPONENT_INDEX}', response code '{RC}'",
+ "EID", eid, "COMPONENT_INDEX", componentIndex, "RC", rc);
+ return;
+ }
+
+ rc = updateManager->handler.registerRequest(
+ eid, instanceId, PLDM_FWUP, PLDM_CANCEL_UPDATE_COMPONENT,
+ std::move(request),
+ [this](mctp_eid_t eid, const pldm_msg* response, size_t respMsgLen) {
+ this->cancelUpdateComponent(eid, response, respMsgLen);
+ });
+ if (rc)
+ {
+ error(
+ "Failed to send cancel update component request for endpoint ID '{EID}', component index '{COMPONENT_INDEX}', response code '{RC}'",
+ "EID", eid, "COMPONENT_INDEX", componentIndex, "RC", rc);
+ }
+}
+
+void DeviceUpdater::cancelUpdateComponent(
+ mctp_eid_t eid, const pldm_msg* response, size_t respMsgLen)
+{
+ // Check if response is valid
+ if (response == nullptr || !respMsgLen)
+ {
+ error(
+ "No response received for cancel update component for endpoint ID '{EID}'",
+ "EID", eid);
+ return;
+ }
+
+ uint8_t completionCode = 0;
+ auto rc = decode_cancel_update_component_resp(response, respMsgLen,
+ &completionCode);
+ if (rc)
+ {
+ error(
+ "Failed to decode cancel update component response for endpoint ID '{EID}', component index '{COMPONENT_INDEX}', completion code '{CC}'",
+ "EID", eid, "COMPONENT_INDEX", componentIndex, "CC",
+ completionCode);
+ return;
+ }
+ if (completionCode)
+ {
+ error(
+ "Failed to cancel update component for endpoint ID '{EID}', component index '{COMPONENT_INDEX}', completion code '{CC}'",
+ "EID", eid, "COMPONENT_INDEX", componentIndex, "CC",
+ completionCode);
+ return;
+ }
+
+ const auto& applicableComponents =
+ std::get<ApplicableComponents>(fwDeviceIDRecord);
+ // Check if this is the last component being cancelled
+ if (componentIndex == applicableComponents.size() - 1)
+ {
+ for (auto& compStatus : componentUpdateStatus)
+ {
+ if (compStatus.second)
+ {
+ // If at least one component update succeeded, proceed with
+ // activation
+ componentIndex = 0;
+ componentUpdateStatus.clear();
+ pldmRequest = std::make_unique<sdeventplus::source::Defer>(
+ updateManager->event,
+ std::bind(&DeviceUpdater::sendActivateFirmwareRequest,
+ this));
+ return;
+ }
+ }
+ updateManager->updateDeviceCompletion(eid, false);
+ }
+ else
+ {
+ // Move to next component and update its status
+ componentIndex++;
+ componentUpdateStatus[componentIndex] = true;
+ pldmRequest = std::make_unique<sdeventplus::source::Defer>(
+ updateManager->event,
+ std::bind(&DeviceUpdater::sendUpdateComponentRequest, this,
+ componentIndex));
+ }
+ return;
+}
+
} // namespace fw_update
} // namespace pldm
diff --git a/fw-update/device_updater.hpp b/fw-update/device_updater.hpp
index 4613a7c..11570a1 100644
--- a/fw-update/device_updater.hpp
+++ b/fw-update/device_updater.hpp
@@ -15,6 +15,12 @@
namespace fw_update
{
+/** @brief Type alias for component update status tracking
+ * Maps component index to its update completion status (true indicates
+ * successful completion, false indicates cancellation)
+ */
+using ComponentUpdateStatusMap = std::map<size_t, bool>;
+
class UpdateManager;
/** @class DeviceUpdater
@@ -152,6 +158,15 @@
*/
void activateFirmware(mctp_eid_t eid, const pldm_msg* response,
size_t respMsgLen);
+ /**
+ * @brief Handler for CancelUpdateComponent command response
+ *
+ * @param[in] eid - Remote MCTP endpoint
+ * @param[in] response - PLDM Response message
+ * @param[in] respMsgLen - Response message length
+ */
+ void cancelUpdateComponent(mctp_eid_t eid, const pldm_msg* response,
+ size_t respMsgLen);
private:
/** @brief Send PassComponentTable command request
@@ -169,6 +184,11 @@
/** @brief Send ActivateFirmware command request */
void sendActivateFirmwareRequest();
+ /**
+ * @brief Send cancel update component request
+ */
+ void sendCancelUpdateComponentRequest();
+
/** @brief Endpoint ID of the firmware device */
mctp_eid_t eid;
@@ -207,6 +227,12 @@
/** @brief To send a PLDM request after the current command handling */
std::unique_ptr<sdeventplus::source::Defer> pldmRequest;
+
+ /**
+ * @brief Map to hold component update status. True - success, False -
+ * cancelled
+ */
+ ComponentUpdateStatusMap componentUpdateStatus;
};
} // namespace fw_update