blob: 6053a89fa44a6168769572fe7edf47d8b5d9509f [file]
#include "item_update_manager.hpp"
#include "activation.hpp"
#include "common/start_lifetime_as.hpp"
#include "common/utils.hpp"
#include "package_parser.hpp"
#include "systemd_interface.hpp"
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <phosphor-logging/lg2.hpp>
#include <cassert>
#include <cmath>
#include <cstdlib>
#include <span>
#include <spanstream>
#include <string>
#include <system_error>
PHOSPHOR_LOG2_USING;
namespace pldm::fw_update
{
constexpr auto APPLY_TIME_ARG_KEY = "applyTime";
bool ItemUpdateManager::processPackage()
{
inProgressActivation = std::make_unique<Activation>(
pldm::utils::DBusHandler::getBus(), objPathWithSwId,
software::Activation::Activations::NotReady, this);
if (packageMap->getSize() < sizeof(pldm_package_header_information))
{
error(
"PLDM fw update package length {SIZE} less than the length of the package header information '{PACKAGE_HEADER_INFO_SIZE}'.",
"SIZE", packageMap->getSize(), "PACKAGE_HEADER_INFO_SIZE",
sizeof(pldm_package_header_information));
inProgressActivation->activation(
software::Activation::Activations::Invalid);
packageMap.reset();
return false;
}
auto buffer = std::vector<uint8_t>(packageMap->getBytes().begin(),
packageMap->getBytes().end());
parser = parsePkgHeader(buffer);
if (parser == nullptr)
{
error("Invalid PLDM package header information");
inProgressActivation->activation(
software::Activation::Activations::Invalid);
packageMap.reset();
return false;
}
try
{
parser->parse(buffer, buffer.size());
}
catch (const std::exception& e)
{
error("Invalid PLDM package header, error - {ERROR}", "ERROR", e);
inProgressActivation->activation(
software::Activation::Activations::Invalid);
parser.reset();
packageMap.reset();
return false;
}
auto deviceIdRecordOffset =
associatePkgToDevice(parser->getFwDeviceIDRecords(), descriptors);
if (!deviceIdRecordOffset)
{
error("Failed to associate package to device");
inProgressActivation->activation(
software::Activation::Activations::Invalid);
packageMap.reset();
return false;
}
const auto& fwDeviceIDRecords = parser->getFwDeviceIDRecords();
const auto& compImageInfos = parser->getComponentImageInfos();
static constexpr uint32_t MAXIMUM_TRANSFER_SIZE = 4096;
auto packageSpan = packageMap->getChars();
packageDataStream =
std::make_unique<std::ispanstream>(packageSpan, std::ios::binary);
deviceUpdater = std::make_unique<DeviceUpdater>(
eid, *packageDataStream, fwDeviceIDRecords[*deviceIdRecordOffset],
compImageInfos, componentInfo, MAXIMUM_TRANSFER_SIZE, this);
inProgressActivation->activation(software::Activation::Activations::Ready);
if (!preConditionPath.empty())
{
SystemdInterface::getInstance(pldm::utils::DBusHandler::getBus())
.execute(preConditionPath, conditionArg,
[this, alive = std::weak_ptr(aliveToken)](bool success) {
if (alive.expired())
{
// This manager was erased while the condition was
// running
return;
}
if (!updateInProgress)
{
return;
}
if (!success)
{
error("Pre-update condition failed for {PATH}",
"PATH", preConditionPath);
completeUpdate(false);
return;
}
startFirmwareUpdate();
});
return true;
}
startFirmwareUpdate();
return true;
}
std::string ItemUpdateManager::processFd(int fd)
{
objPathWithSwId = std::format("{}_{}", objPath, utils::generateSwId());
auto rawDupFd = dup(fd);
if (rawDupFd < 0)
{
error("Failed to duplicate package file descriptor");
throw sdbusplus::xyz::openbmc_project::Common::Error::Unavailable();
}
this->dupFd = std::make_unique<pldm::utils::CustomFD>(rawDupFd);
deferHandler = std::make_unique<
sdeventplus::source::Defer>(event, [this](sdeventplus::source::
EventBase&) {
try
{
packageMap =
std::make_unique<pldm::utils::MMapHandler>((*this->dupFd)());
}
catch (const std::exception& e)
{
error("Failed to mmap package file, error - {ERROR}", "ERROR", e);
teardownUpdate();
throw sdbusplus::xyz::openbmc_project::Common::Error::Unavailable();
}
if (!processPackage())
{
error("Failed to process firmware update package");
teardownUpdate();
throw sdbusplus::xyz::openbmc_project::Common::Error::Unavailable();
}
});
return objPathWithSwId;
}
std::optional<DeviceIDRecordOffset> ItemUpdateManager::associatePkgToDevice(
const FirmwareDeviceIDRecords& fwDeviceIDRecords,
const Descriptors& descriptors)
{
for (size_t index = 0; index < fwDeviceIDRecords.size(); ++index)
{
const auto& deviceIDDescriptors =
std::get<Descriptors>(fwDeviceIDRecords[index]);
if (std::includes(descriptors.begin(), descriptors.end(),
deviceIDDescriptors.begin(),
deviceIDDescriptors.end()))
{
return index;
}
}
return std::nullopt;
}
void ItemUpdateManager::updateDeviceCompletion(mctp_eid_t /*eid*/, bool status)
{
if (!postConditionPath.empty() && status == true)
{
SystemdInterface::getInstance(pldm::utils::DBusHandler::getBus())
.execute(postConditionPath, conditionArg,
[this, status, alive = std::weak_ptr(aliveToken)](
bool conditionSuccess) {
if (alive.expired())
{
// This manager was erased while the condition was
// running
return;
}
if (!updateInProgress)
{
return;
}
if (!conditionSuccess)
{
error("Post-update condition failed for {PATH}",
"PATH", postConditionPath);
}
completeUpdate(status && conditionSuccess);
});
return;
}
completeUpdate(status);
}
void ItemUpdateManager::startFirmwareUpdate()
{
if (!updateInProgress)
{
return;
}
activationProgress = std::make_unique<ActivationProgress>(
pldm::utils::DBusHandler::getBus(), objPathWithSwId);
inProgressActivation->activation(
software::Activation::Activations::Activating);
}
void ItemUpdateManager::completeUpdate(bool status)
{
if (!updateInProgress)
{
return;
}
// A failing pre-update condition completes the update before the firmware
// update flow starts, so neither the progress interface nor the start time
// is necessarily available here.
if (activationProgress)
{
activationProgress->progress(100);
activationProgress.reset();
}
if (startTime)
{
auto dur = std::chrono::duration<double, std::milli>(
std::chrono::steady_clock::now() - *startTime)
.count();
info("Firmware update time: {DURATION}ms", "DURATION", dur);
}
inProgressActivation->activation(
status ? software::Activation::Activations::Active
: software::Activation::Activations::Failed);
teardownUpdate();
if (taskCompletionCallback)
{
taskCompletionCallback();
}
}
void ItemUpdateManager::teardownUpdate()
{
deviceUpdater.reset();
packageDataStream.reset();
packageMap.reset();
dupFd.reset();
updateInProgress = false;
}
Response ItemUpdateManager::handleRequest(
mctp_eid_t eid, uint8_t command, const pldm_msg* request, size_t reqMsgLen)
{
Response response(sizeof(pldm_msg), 0);
if (eid != this->eid || !deviceUpdater)
{
auto ptr = std::start_lifetime_as<pldm_msg>(response.data());
auto rc = encode_cc_only_resp(request->hdr.instance_id,
request->hdr.type, request->hdr.command,
PLDM_FWUP_COMMAND_NOT_EXPECTED, ptr);
assert(rc == PLDM_SUCCESS);
return response;
}
if (command == PLDM_REQUEST_FIRMWARE_DATA)
{
return deviceUpdater->requestFwData(request, reqMsgLen);
}
else if (command == PLDM_TRANSFER_COMPLETE)
{
return deviceUpdater->transferComplete(request, reqMsgLen);
}
else if (command == PLDM_VERIFY_COMPLETE)
{
return deviceUpdater->verifyComplete(request, reqMsgLen);
}
else if (command == PLDM_APPLY_COMPLETE)
{
return deviceUpdater->applyComplete(request, reqMsgLen);
}
auto ptr = std::start_lifetime_as<pldm_msg>(response.data());
auto rc =
encode_cc_only_resp(request->hdr.instance_id, request->hdr.type,
request->hdr.command, PLDM_ERROR_INVALID_DATA, ptr);
assert(rc == PLDM_SUCCESS);
return response;
}
void ItemUpdateManager::activatePackage()
{
startTime = std::chrono::steady_clock::now();
deviceUpdater->startFwUpdateFlow();
}
void ItemUpdateManager::resetActivationState()
{
inProgressActivation.reset();
activationProgress.reset();
dupFd.reset();
updateInProgress = false;
}
void ItemUpdateManager::updateActivationProgress()
{
if (deviceUpdater)
{
auto progress = deviceUpdater->getProgress();
if (progress != lastProgress)
{
activationProgress->progress(progress);
lastProgress = progress;
}
}
}
sdbusplus::object_path ItemUpdateManager::startUpdate(
sdbusplus::message::unix_fd image,
ApplyTimeIntf::RequestedApplyTimes applyTime)
{
if (updateInProgress)
{
error("Update already in progress");
throw sdbusplus::xyz::openbmc_project::Common::Error::Unavailable();
}
if (image.fd < 0)
{
error("Invalid package file descriptor");
throw sdbusplus::xyz::openbmc_project::Common::Error::Unavailable();
}
updateInProgress = true;
this->applyTime = applyTime;
// Every parameterized condition service receives all supported named
// arguments, so the requested apply time is always appended here.
conditionArg = baseConditionArg;
if (!conditionArg.empty())
{
conditionArg += ",";
}
conditionArg +=
std::format("{}={}", APPLY_TIME_ARG_KEY, applyTimeToString());
return processFd(image.fd);
}
std::string ItemUpdateManager::applyTimeToString() const
{
constexpr std::string_view applyTimePrefix =
"xyz.openbmc_project.Software.ApplyTime.RequestedApplyTimes.";
auto applyTimeStr =
ApplyTimeIntf::convertRequestedApplyTimesToString(applyTime);
if (applyTimeStr.starts_with(applyTimePrefix))
{
return applyTimeStr.substr(applyTimePrefix.size());
}
return applyTimeStr;
}
} // namespace pldm::fw_update