| #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 |