| #include <byteswap.h> |
| #include <ipmid/api.h> |
| #include <openssl/evp.h> |
| #include <openssl/sha.h> |
| #include <sys/mman.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| #include <unistd.h> |
| |
| #include <appcommands.hpp> |
| #include <boost/algorithm/string.hpp> |
| #include <boost/container/flat_map.hpp> |
| #include <boost/process/child.hpp> |
| #include <boost/uuid/random_generator.hpp> |
| #include <boost/uuid/uuid_io.hpp> |
| #include <commandutils.hpp> |
| #include <ipmid/api.hpp> |
| #include <ipmid/utils.hpp> |
| #include <phosphor-logging/log.hpp> |
| #include <sdbusplus/bus.hpp> |
| #include <sdbusplus/bus/match.hpp> |
| #include <sdbusplus/server/object.hpp> |
| #include <sdbusplus/timer.hpp> |
| #include <types.hpp> |
| |
| #include <chrono> |
| #include <cstdint> |
| #include <filesystem> |
| #include <fstream> |
| #include <iostream> |
| #include <map> |
| #include <random> |
| #ifdef INTEL_PFR_ENABLED |
| #include <spiDev.hpp> |
| #endif |
| |
| static constexpr int openSslSuccess = 1; |
| static constexpr bool DEBUG = true; |
| static void registerFirmwareFunctions() __attribute__((constructor)); |
| |
| namespace ipmi |
| { |
| namespace firmware |
| { |
| constexpr Cmd cmdGetFwVersionInfo = 0x20; |
| constexpr Cmd cmdGetFwSecurityVersionInfo = 0x21; |
| constexpr Cmd cmdGetFwUpdateChannelInfo = 0x22; |
| constexpr Cmd cmdGetBmcExecutionContext = 0x23; |
| constexpr Cmd cmdFwGetRootCertData = 0x25; |
| constexpr Cmd cmdGetFwUpdateRandomNumber = 0x26; |
| constexpr Cmd cmdSetFirmwareUpdateMode = 0x27; |
| constexpr Cmd cmdExitFirmwareUpdateMode = 0x28; |
| constexpr Cmd cmdGetSetFwUpdateControl = 0x29; |
| constexpr Cmd cmdGetFirmwareUpdateStatus = 0x2A; |
| constexpr Cmd cmdSetFirmwareUpdateOptions = 0x2B; |
| constexpr Cmd cmdFwImageWriteData = 0x2c; |
| } // namespace firmware |
| } // namespace ipmi |
| |
| namespace ipmi |
| { |
| // Custom completion codes |
| constexpr Cc ccUsbAttachOrDetachFailed = 0x80; |
| constexpr Cc ccNotSupportedInPresentState = 0xD5; |
| |
| static inline auto responseUsbAttachOrDetachFailed() |
| { |
| return response(ccUsbAttachOrDetachFailed); |
| } |
| static inline auto responseNotSupportedInPresentState() |
| { |
| return response(ccNotSupportedInPresentState); |
| } |
| } // namespace ipmi |
| |
| static constexpr size_t imageCount = 2; |
| std::array<std::array<uint8_t, imageCount>, imageCount> imgFwSecurityVersion = { |
| (0, 0), (0, 0)}; |
| static constexpr size_t svnActiveVerOffsetInPfm = 0x404; |
| static constexpr size_t bkcActiveVerOffsetInPfm = 0x405; |
| static constexpr size_t svnRecoveryVerOffsetInPfm = 0x804; |
| static constexpr size_t bkcRecoveryVerOffsetInPfm = 0x805; |
| static constexpr const char* bmcStateIntf = "xyz.openbmc_project.State.BMC"; |
| static constexpr const char* bmcStatePath = "/xyz/openbmc_project/state/bmc0"; |
| static constexpr const char* bmcStateReady = |
| "xyz.openbmc_project.State.BMC.BMCState.Ready"; |
| static constexpr const char* bmcStateUpdateInProgress = |
| "xyz.openbmc_project.State.BMC.BMCState.UpdateInProgress"; |
| |
| static constexpr char firmwareBufferFile[] = "/tmp/fw-download.bin"; |
| static std::chrono::steady_clock::time_point fwRandomNumGenTs; |
| static constexpr auto fwRandomNumExpirySeconds = std::chrono::seconds(30); |
| static constexpr size_t fwRandomNumLength = 8; |
| static std::array<uint8_t, fwRandomNumLength> fwRandomNum; |
| constexpr char usbCtrlPath[] = "/usr/bin/usb-ctrl"; |
| constexpr char fwUpdateMountPoint[] = "/tmp/usb-fwupd.mnt"; |
| constexpr char fwUpdateUsbVolImage[] = "/tmp/usb-fwupd.img"; |
| constexpr char fwUpdateUSBDevName[] = "fw-usb-mass-storage-dev"; |
| constexpr size_t fwPathMaxLength = 255; |
| |
| #ifdef INTEL_PFR_ENABLED |
| uint32_t imgLength = 0; |
| uint32_t imgType = 0; |
| bool block0Mapped = false; |
| static constexpr uint32_t perBlock0MagicNum = 0xB6EAFD19; |
| |
| static constexpr const char* bmcActivePfmMTDDev = "/dev/mtd/pfm"; |
| static constexpr const char* bmcRecoveryImgMTDDev = "/dev/mtd/rc-image"; |
| static constexpr size_t pfmBaseOffsetInImage = 0x400; |
| static constexpr size_t rootkeyOffsetInPfm = 0xA0; |
| static constexpr size_t cskKeyOffsetInPfm = 0x124; |
| static constexpr size_t cskSignatureOffsetInPfm = 0x19c; |
| static constexpr size_t certKeyLen = 96; |
| static constexpr size_t cskSignatureLen = 96; |
| |
| static constexpr const char* versionIntf = |
| "xyz.openbmc_project.Software.Version"; |
| |
| enum class FwGetRootCertDataTag : uint8_t |
| { |
| activeRootKey = 1, |
| recoveryRootKey, |
| activeCSK, |
| recoveryCSK, |
| }; |
| |
| enum class FWDeviceIDTag : uint8_t |
| { |
| bmcActiveImage = 1, |
| bmcRecoveryImage, |
| }; |
| |
| const static boost::container::flat_map<FWDeviceIDTag, const char*> |
| fwVersionIdMap{{FWDeviceIDTag::bmcActiveImage, |
| "/xyz/openbmc_project/software/bmc_active"}, |
| {FWDeviceIDTag::bmcRecoveryImage, |
| "/xyz/openbmc_project/software/bmc_recovery"}}; |
| #endif // INTEL_PFR_ENABLED |
| |
| enum class ChannelIdTag : uint8_t |
| { |
| reserved = 0, |
| kcs = 1, |
| ipmb = 2, |
| rmcpPlus = 3 |
| }; |
| |
| enum class BmcExecutionContext : uint8_t |
| { |
| reserved = 0, |
| linuxOs = 0x10, |
| bootLoader = 0x11, |
| }; |
| |
| enum class FwUpdateCtrlReq : uint8_t |
| { |
| getCurrentControlStatus = 0x00, |
| imageTransferStart = 0x01, |
| imageTransferComplete = 0x02, |
| imageTransferAbort = 0x03, |
| setFirmwareFilename = 0x04, |
| attachUsbDevice = 0x05, |
| detachUsbDevice = 0x06 |
| }; |
| |
| constexpr std::size_t operator""_MB(unsigned long long v) |
| { |
| return 1024u * 1024u * v; |
| } |
| static constexpr size_t maxFirmwareImageSize = 32_MB; |
| |
| static bool localDownloadInProgress(void) |
| { |
| struct stat sb; |
| if (stat(firmwareBufferFile, &sb) < 0) |
| { |
| return false; |
| } |
| return true; |
| } |
| |
| class TransferHashCheck |
| { |
| public: |
| enum class HashCheck : uint8_t |
| { |
| notRequested = 0, |
| requested, |
| sha2Success, |
| sha2Failed = 0xe2, |
| }; |
| |
| protected: |
| std::unique_ptr<EVP_MD_CTX, std::function<void(EVP_MD_CTX*)>> ctx; |
| std::vector<uint8_t> expectedHash; |
| HashCheck check; |
| |
| public: |
| TransferHashCheck(const std::vector<uint8_t>& expected) : |
| ctx(EVP_MD_CTX_new(), &EVP_MD_CTX_free), expectedHash(expected) |
| { |
| if (!ctx) |
| { |
| throw std::runtime_error("Unable to allocate for ctx."); |
| } |
| |
| if (EVP_DigestInit(ctx.get(), EVP_sha256()) != openSslSuccess) |
| { |
| throw std::runtime_error("Unable to allocate for ctx."); |
| } |
| |
| check = HashCheck::requested; |
| } |
| |
| ~TransferHashCheck() |
| {} |
| |
| bool hash(const std::vector<uint8_t>& data) |
| { |
| if (EVP_DigestUpdate(ctx.get(), data.data(), data.size()) != |
| openSslSuccess) |
| { |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool clear() |
| { |
| /* |
| * EVP_DigestInit() always uses the default digest implementation and |
| * calls EVP_MD_CTX_reset(). |
| */ |
| if (EVP_DigestInit(ctx.get(), EVP_sha256()) != openSslSuccess) |
| { |
| return false; |
| } |
| |
| return true; |
| } |
| |
| enum HashCheck verify() |
| { |
| unsigned int len = 0; |
| std::vector<uint8_t> digest(EVP_MD_size(EVP_sha256())); |
| |
| check = HashCheck::sha2Failed; |
| |
| if (EVP_DigestFinal(ctx.get(), digest.data(), &len) == openSslSuccess) |
| { |
| if (digest == expectedHash) |
| { |
| phosphor::logging::log<phosphor::logging::level::INFO>( |
| "Transfer sha2 verify passed."); |
| check = HashCheck::sha2Success; |
| } |
| else |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "Transfer sha2 verify failed."); |
| check = HashCheck::sha2Failed; |
| } |
| } |
| |
| return check; |
| } |
| |
| uint8_t status() const |
| { |
| return static_cast<uint8_t>(check); |
| } |
| }; |
| |
| class MappedFile |
| { |
| public: |
| MappedFile(const std::string& fname) : addr(nullptr), fsize(0) |
| { |
| std::error_code ec; |
| size_t sz = std::filesystem::file_size(fname, ec); |
| if (!ec) |
| { |
| return; |
| } |
| int fd = open(fname.c_str(), O_RDONLY); |
| if (fd < 0) |
| { |
| return; |
| } |
| void* tmp = mmap(NULL, sz, PROT_READ, MAP_SHARED, fd, 0); |
| close(fd); |
| if (tmp == MAP_FAILED) |
| { |
| return; |
| } |
| addr = tmp; |
| fsize = sz; |
| } |
| |
| ~MappedFile() |
| { |
| if (addr) |
| { |
| munmap(addr, fsize); |
| } |
| } |
| const uint8_t* data() const |
| { |
| return static_cast<const uint8_t*>(addr); |
| } |
| size_t size() const |
| { |
| return fsize; |
| } |
| |
| private: |
| size_t fsize; |
| void* addr; |
| }; |
| |
| class FwUpdateStatusCache |
| { |
| public: |
| enum |
| { |
| fwStateInit = 0, |
| fwStateIdle, |
| fwStateDownload, |
| fwStateVerify, |
| fwStateProgram, |
| fwStateUpdateSuccess, |
| fwStateError = 0x0f, |
| fwStateAcCycleRequired = 0x83, |
| }; |
| uint8_t getState() |
| { |
| if ((fwUpdateState == fwStateIdle || fwUpdateState == fwStateInit) && |
| localDownloadInProgress()) |
| { |
| fwUpdateState = fwStateDownload; |
| progressPercent = 0; |
| } |
| return fwUpdateState; |
| } |
| void resetStatusCache() |
| { |
| unlink(firmwareBufferFile); |
| } |
| void setState(const uint8_t state) |
| { |
| switch (state) |
| { |
| case fwStateInit: |
| case fwStateIdle: |
| case fwStateError: |
| resetStatusCache(); |
| break; |
| case fwStateDownload: |
| case fwStateVerify: |
| case fwStateProgram: |
| case fwStateUpdateSuccess: |
| break; |
| default: |
| // Error |
| break; |
| } |
| fwUpdateState = state; |
| } |
| uint8_t percent() |
| { |
| return progressPercent; |
| } |
| void updateActivationPercent(const std::string& objPath) |
| { |
| std::shared_ptr<sdbusplus::asio::connection> busp = getSdBus(); |
| fwUpdateState = fwStateProgram; |
| progressPercent = 0; |
| match = std::make_shared<sdbusplus::bus::match::match>( |
| *busp, |
| sdbusplus::bus::match::rules::propertiesChanged( |
| objPath, "xyz.openbmc_project.Software.ActivationProgress"), |
| [&](sdbusplus::message::message& msg) { |
| std::map<std::string, ipmi::DbusVariant> props; |
| std::vector<std::string> inVal; |
| std::string iface; |
| try |
| { |
| msg.read(iface, props, inVal); |
| } |
| catch (const std::exception& e) |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "Exception caught in get ActivationProgress"); |
| return; |
| } |
| |
| auto it = props.find("Progress"); |
| if (it != props.end()) |
| { |
| progressPercent = std::get<uint8_t>(it->second); |
| if (progressPercent == 100) |
| { |
| fwUpdateState = fwStateUpdateSuccess; |
| } |
| } |
| }); |
| } |
| uint8_t activationTimerTimeout() |
| { |
| phosphor::logging::log<phosphor::logging::level::INFO>( |
| "activationTimerTimeout: Increase percentage...", |
| phosphor::logging::entry("PERCENT:%d", progressPercent)); |
| progressPercent = progressPercent + 5; |
| if (progressPercent > 95) |
| { |
| /*changing the state to ready to update firmware utility */ |
| fwUpdateState = fwStateUpdateSuccess; |
| } |
| return progressPercent; |
| } |
| /* API for changing state to ERROR */ |
| void firmwareUpdateAbortState() |
| { |
| unlink(firmwareBufferFile); |
| // changing the state to error |
| fwUpdateState = fwStateError; |
| } |
| void setDeferRestart(bool deferRestart) |
| { |
| deferRestartState = deferRestart; |
| } |
| void setInhibitDowngrade(bool inhibitDowngrade) |
| { |
| inhibitDowngradeState = inhibitDowngrade; |
| } |
| bool getDeferRestart() |
| { |
| return deferRestartState; |
| } |
| bool getInhibitDowngrade() |
| { |
| return inhibitDowngradeState; |
| } |
| |
| protected: |
| std::shared_ptr<sdbusplus::asio::connection> busp; |
| std::shared_ptr<sdbusplus::bus::match::match> match; |
| uint8_t fwUpdateState = 0; |
| uint8_t progressPercent = 0; |
| bool deferRestartState = false; |
| bool inhibitDowngradeState = false; |
| }; |
| |
| static FwUpdateStatusCache fwUpdateStatus; |
| std::unique_ptr<TransferHashCheck> xferHashCheck; |
| |
| static void activateImage(const std::string& objPath) |
| { |
| // If flag is false means to reboot |
| if (fwUpdateStatus.getDeferRestart() == false) |
| { |
| phosphor::logging::log<phosphor::logging::level::INFO>( |
| "activating Image: ", |
| phosphor::logging::entry("OBJPATH =%s", objPath.c_str())); |
| std::shared_ptr<sdbusplus::asio::connection> bus = getSdBus(); |
| bus->async_method_call( |
| [](const boost::system::error_code ec) { |
| if (ec) |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "async_method_call error: activateImage failed"); |
| return; |
| } |
| }, |
| "xyz.openbmc_project.Software.BMC.Updater", objPath, |
| "org.freedesktop.DBus.Properties", "Set", |
| "xyz.openbmc_project.Software.Activation", "RequestedActivation", |
| std::variant<std::string>("xyz.openbmc_project.Software.Activation." |
| "RequestedActivations.Active")); |
| } |
| else |
| { |
| phosphor::logging::log<phosphor::logging::level::INFO>( |
| "Firmware image activation is deferred."); |
| } |
| fwUpdateStatus.setState( |
| static_cast<uint8_t>(FwUpdateStatusCache::fwStateUpdateSuccess)); |
| } |
| |
| static bool getFirmwareUpdateMode() |
| { |
| std::shared_ptr<sdbusplus::asio::connection> busp = getSdBus(); |
| try |
| { |
| auto service = ipmi::getService(*busp, bmcStateIntf, bmcStatePath); |
| ipmi::Value state = ipmi::getDbusProperty( |
| *busp, service, bmcStatePath, bmcStateIntf, "CurrentBMCState"); |
| std::string bmcState = std::get<std::string>(state); |
| return (bmcState == bmcStateUpdateInProgress); |
| } |
| catch (const std::exception& e) |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "Exception caught while getting BMC state.", |
| phosphor::logging::entry("EXCEPTION=%s", e.what())); |
| throw; |
| } |
| } |
| |
| static void setFirmwareUpdateMode(const bool mode) |
| { |
| |
| std::string bmcState(bmcStateReady); |
| if (mode) |
| { |
| bmcState = bmcStateUpdateInProgress; |
| } |
| |
| std::shared_ptr<sdbusplus::asio::connection> busp = getSdBus(); |
| try |
| { |
| auto service = ipmi::getService(*busp, bmcStateIntf, bmcStatePath); |
| ipmi::setDbusProperty(*busp, service, bmcStatePath, bmcStateIntf, |
| "CurrentBMCState", bmcState); |
| } |
| catch (const std::exception& e) |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "Exception caught while setting BMC state.", |
| phosphor::logging::entry("EXCEPTION=%s", e.what())); |
| throw; |
| } |
| } |
| |
| /** @brief check if channel IPMB |
| * |
| * This function checks if the command is from IPMB |
| * |
| * @param[in] ctx - context of current session. |
| * @returns true if the medium is IPMB else return true. |
| **/ |
| ipmi::Cc checkIPMBChannel(const ipmi::Context::ptr& ctx, bool& isIPMBChannel) |
| { |
| ipmi::ChannelInfo chInfo; |
| |
| if (ipmi::getChannelInfo(ctx->channel, chInfo) != ipmi::ccSuccess) |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "Failed to get Channel Info", |
| phosphor::logging::entry("CHANNEL=%d", ctx->channel)); |
| return ipmi::ccUnspecifiedError; |
| } |
| |
| if (static_cast<ipmi::EChannelMediumType>(chInfo.mediumType) == |
| ipmi::EChannelMediumType::ipmb) |
| { |
| isIPMBChannel = true; |
| } |
| return ipmi::ccSuccess; |
| } |
| |
| static void postTransferCompleteHandler( |
| std::unique_ptr<sdbusplus::bus::match::match>& fwUpdateMatchSignal) |
| { |
| // Setup timer for watching signal |
| static phosphor::Timer timer( |
| [&fwUpdateMatchSignal]() { fwUpdateMatchSignal = nullptr; }); |
| |
| static phosphor::Timer activationStatusTimer([]() { |
| if (fwUpdateStatus.activationTimerTimeout() > 95) |
| { |
| activationStatusTimer.stop(); |
| fwUpdateStatus.setState( |
| static_cast<uint8_t>(FwUpdateStatusCache::fwStateVerify)); |
| } |
| }); |
| |
| timer.start(std::chrono::microseconds(5000000), false); |
| |
| // callback function for capturing signal |
| auto callback = [&](sdbusplus::message::message& m) { |
| bool flag = false; |
| |
| std::vector<std::pair< |
| std::string, |
| std::vector<std::pair<std::string, std::variant<std::string>>>>> |
| intfPropsPair; |
| sdbusplus::message::object_path objPath; |
| |
| try |
| { |
| m.read(objPath, intfPropsPair); // Read in the object path |
| // that was just created |
| } |
| catch (const std::exception& e) |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "Exception caught in reading created object path."); |
| return; |
| } |
| // constructing response message |
| phosphor::logging::log<phosphor::logging::level::INFO>( |
| "New Interface Added.", |
| phosphor::logging::entry("OBJPATH=%s", objPath.str.c_str())); |
| for (auto& interface : intfPropsPair) |
| { |
| if (interface.first == "xyz.openbmc_project.Software.Activation") |
| { |
| // There are chances of getting two signals for |
| // InterfacesAdded. So cross check and discrad second instance. |
| if (fwUpdateMatchSignal == nullptr) |
| { |
| return; |
| } |
| // Found our interface, disable callbacks |
| fwUpdateMatchSignal = nullptr; |
| |
| phosphor::logging::log<phosphor::logging::level::INFO>( |
| "Start activationStatusTimer for status."); |
| try |
| { |
| timer.stop(); |
| activationStatusTimer.start( |
| std::chrono::microseconds(3000000), true); |
| } |
| catch (const std::exception& e) |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "Exception caught in start activationStatusTimer.", |
| phosphor::logging::entry("ERROR=%s", e.what())); |
| } |
| |
| fwUpdateStatus.updateActivationPercent(objPath.str); |
| activateImage(objPath.str); |
| } |
| } |
| }; |
| |
| // Adding matcher |
| fwUpdateMatchSignal = std::make_unique<sdbusplus::bus::match::match>( |
| *getSdBus(), |
| "interface='org.freedesktop.DBus.ObjectManager',type='signal'," |
| "member='InterfacesAdded',path='/xyz/openbmc_project/software'", |
| callback); |
| } |
| static bool startFirmwareUpdate(const std::string& uri) |
| { |
| // fwupdate URIs start with file:// or usb:// or tftp:// etc. By the time |
| // the code gets to this point, the file should be transferred start the |
| // request (creating a new file in /tmp/images causes the update manager to |
| // check if it is ready for activation) |
| static std::unique_ptr<sdbusplus::bus::match::match> fwUpdateMatchSignal; |
| postTransferCompleteHandler(fwUpdateMatchSignal); |
| std::filesystem::rename( |
| uri, "/tmp/images/" + |
| boost::uuids::to_string(boost::uuids::random_generator()())); |
| return true; |
| } |
| |
| static bool transferImageFromFile(const std::string& uri, bool move = true) |
| { |
| std::error_code ec; |
| phosphor::logging::log<phosphor::logging::level::INFO>( |
| "Transfer Image From File.", |
| phosphor::logging::entry("URI=%s", uri.c_str())); |
| if (move) |
| { |
| std::filesystem::rename(uri, firmwareBufferFile, ec); |
| } |
| else |
| { |
| std::filesystem::copy(uri, firmwareBufferFile, |
| std::filesystem::copy_options::overwrite_existing, |
| ec); |
| } |
| if (xferHashCheck) |
| { |
| MappedFile mappedfw(uri); |
| if (!xferHashCheck->hash( |
| {mappedfw.data(), mappedfw.data() + mappedfw.size()})) |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "transferImageFromFile: xferHashCheck->hash failed."); |
| return false; |
| } |
| } |
| if (ec.value()) |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "Image copy failed."); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| template <typename... ArgTypes> |
| static int executeCmd(const char* path, ArgTypes&&... tArgs) |
| { |
| boost::process::child execProg(path, const_cast<char*>(tArgs)...); |
| execProg.wait(); |
| return execProg.exit_code(); |
| } |
| |
| static bool transferImageFromUsb(const std::string& uri) |
| { |
| bool ret = false; |
| char fwpath[fwPathMaxLength]; |
| |
| phosphor::logging::log<phosphor::logging::level::INFO>( |
| "Transfer Image From USB.", |
| phosphor::logging::entry("URI=%s", uri.c_str())); |
| |
| if (executeCmd(usbCtrlPath, "mount", fwUpdateUsbVolImage, |
| fwUpdateMountPoint) == 0) |
| { |
| std::string usb_path = std::string(fwUpdateMountPoint) + "/" + uri; |
| ret = transferImageFromFile(usb_path, false); |
| |
| executeCmd(usbCtrlPath, "cleanup", fwUpdateUsbVolImage, |
| fwUpdateMountPoint); |
| } |
| |
| return ret; |
| } |
| |
| static bool transferFirmwareFromUri(const std::string& uri) |
| { |
| static constexpr char fwUriFile[] = "file://"; |
| static constexpr char fwUriUsb[] = "usb://"; |
| phosphor::logging::log<phosphor::logging::level::INFO>( |
| "Transfer Image From URI.", |
| phosphor::logging::entry("URI=%s", uri.c_str())); |
| if (boost::algorithm::starts_with(uri, fwUriFile)) |
| { |
| std::string fname = uri.substr(sizeof(fwUriFile) - 1); |
| if (fname != firmwareBufferFile) |
| { |
| return transferImageFromFile(fname); |
| } |
| return true; |
| } |
| if (boost::algorithm::starts_with(uri, fwUriUsb)) |
| { |
| std::string fname = uri.substr(sizeof(fwUriUsb) - 1); |
| return transferImageFromUsb(fname); |
| } |
| return false; |
| } |
| |
| /* Get USB-mass-storage device status: inserted => true, ejected => false */ |
| static bool getUsbStatus() |
| { |
| std::filesystem::path usbDevPath = |
| std::filesystem::path("/sys/kernel/config/usb_gadget") / |
| fwUpdateUSBDevName; |
| return (std::filesystem::exists(usbDevPath) ? true : false); |
| } |
| |
| /* Insert the USB-mass-storage device status: success => 0, failure => non-0 */ |
| static int attachUsbDevice() |
| { |
| if (getUsbStatus()) |
| { |
| return 1; |
| } |
| int ret = executeCmd(usbCtrlPath, "setup", fwUpdateUsbVolImage, |
| std::to_string(maxFirmwareImageSize / 1_MB).c_str()); |
| if (!ret) |
| { |
| ret = executeCmd(usbCtrlPath, "insert", fwUpdateUSBDevName, |
| fwUpdateUsbVolImage); |
| } |
| return ret; |
| } |
| |
| /* Eject the USB-mass-storage device status: success => 0, failure => non-0 */ |
| static int detachUsbDevice() |
| { |
| if (!getUsbStatus()) |
| { |
| return 1; |
| } |
| return executeCmd(usbCtrlPath, "eject", fwUpdateUSBDevName); |
| } |
| static uint8_t getActiveBootImage(ipmi::Context::ptr ctx) |
| { |
| constexpr uint8_t undefinedImage = 0x00; |
| constexpr uint8_t primaryImage = 0x01; |
| constexpr uint8_t secondaryImage = 0x02; |
| constexpr const char* secondaryFitImageStartAddr = "22480000"; |
| |
| uint8_t bootImage = primaryImage; |
| boost::system::error_code ec; |
| std::string value = ctx->bus->yield_method_call<std::string>( |
| ctx->yield, ec, "xyz.openbmc_project.U_Boot.Environment.Manager", |
| "/xyz/openbmc_project/u_boot/environment/mgr", |
| "xyz.openbmc_project.U_Boot.Environment.Manager", "Read", "bootcmd"); |
| if (ec) |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "Failed to read the bootcmd value"); |
| /* don't fail, just give back undefined until it is ready */ |
| bootImage = undefinedImage; |
| } |
| |
| /* cheking for secondary FitImage Address 22480000 */ |
| else if (value.find(secondaryFitImageStartAddr) != std::string::npos) |
| { |
| bootImage = secondaryImage; |
| } |
| else |
| { |
| bootImage = primaryImage; |
| } |
| |
| return bootImage; |
| } |
| |
| #ifdef INTEL_PFR_ENABLED |
| using fwVersionInfoType = std::tuple<uint8_t, // ID Tag |
| uint8_t, // Major Version Number |
| uint8_t, // Minor Version Number |
| uint32_t, // Build Number |
| uint32_t, // Build Timestamp |
| uint32_t>; // Update Timestamp |
| ipmi::RspType<uint8_t, std::vector<fwVersionInfoType>> ipmiGetFwVersionInfo() |
| { |
| // Byte 1 - Count (N) Number of devices data is being returned for. |
| // Bytes 2:16 - Device firmare information(fwVersionInfoType) |
| // Bytes - 17:(15xN) - Repeat of 2 through 16 |
| |
| std::vector<fwVersionInfoType> fwVerInfoList; |
| std::shared_ptr<sdbusplus::asio::connection> busp = getSdBus(); |
| for (const auto& fwDev : fwVersionIdMap) |
| { |
| std::string verStr; |
| try |
| { |
| auto service = ipmi::getService(*busp, versionIntf, fwDev.second); |
| |
| ipmi::Value result = ipmi::getDbusProperty( |
| *busp, service, fwDev.second, versionIntf, "Version"); |
| verStr = std::get<std::string>(result); |
| } |
| catch (const std::exception& e) |
| { |
| phosphor::logging::log<phosphor::logging::level::INFO>( |
| "Failed to fetch Version property", |
| phosphor::logging::entry("ERROR=%s", e.what()), |
| phosphor::logging::entry("PATH=%s", fwDev.second), |
| phosphor::logging::entry("INTERFACE=%s", versionIntf)); |
| continue; |
| } |
| |
| if (verStr.empty()) |
| { |
| phosphor::logging::log<phosphor::logging::level::INFO>( |
| "Version is empty.", |
| phosphor::logging::entry("PATH=%s", fwDev.second), |
| phosphor::logging::entry("INTERFACE=%s", versionIntf)); |
| continue; |
| } |
| |
| uint8_t majorNum = 0; |
| uint8_t minorNum = 0; |
| uint32_t buildNum = 0; |
| try |
| { |
| std::optional<ipmi::MetaRevision> rev = |
| ipmi::convertIntelVersion(verStr); |
| if (rev.has_value()) |
| { |
| ipmi::MetaRevision revision = rev.value(); |
| majorNum = revision.major % 10 + (revision.major / 10) * 16; |
| minorNum = (revision.minor > 99 ? 99 : revision.minor); |
| minorNum = minorNum % 10 + (minorNum / 10) * 16; |
| uint32_t hash = std::stoul(revision.metaHash, 0, 16); |
| hash = bswap_32(hash); |
| buildNum = (revision.buildNo & 0xFF) + (hash & 0xFFFFFF00); |
| } |
| else |
| { |
| std::vector<std::string> splitVer; |
| boost::split(splitVer, verStr, boost::is_any_of(".-")); |
| if (splitVer.size() < 3) |
| { |
| phosphor::logging::log<phosphor::logging::level::INFO>( |
| "Invalid Version format.", |
| phosphor::logging::entry("Version=%s", verStr.c_str()), |
| phosphor::logging::entry("PATH=%s", fwDev.second)); |
| continue; |
| } |
| majorNum = std::stoul(splitVer[0], nullptr, 16); |
| minorNum = std::stoul(splitVer[1], nullptr, 16); |
| buildNum = std::stoul(splitVer[2], nullptr, 16); |
| } |
| // Build Timestamp - Not supported. |
| // Update Timestamp - TODO: Need to check with CPLD team. |
| fwVerInfoList.emplace_back( |
| fwVersionInfoType(static_cast<uint8_t>(fwDev.first), majorNum, |
| minorNum, buildNum, 0, 0)); |
| } |
| catch (const std::exception& e) |
| { |
| phosphor::logging::log<phosphor::logging::level::INFO>( |
| "Failed to convert stoul.", |
| phosphor::logging::entry("ERROR=%s", e.what())); |
| continue; |
| } |
| } |
| |
| return ipmi::responseSuccess(fwVerInfoList.size(), fwVerInfoList); |
| } |
| |
| std::array<uint8_t, imageCount> getSecurityVersionInfo(const char* mtdDevBuf, |
| size_t svnVerOffsetInPfm, |
| size_t bkcVerOffsetInPfm) |
| { |
| constexpr size_t bufLength = 1; |
| std::array<uint8_t, imageCount> fwSecurityVersionBuf = {0}, temp; |
| constexpr uint8_t svnIndexValue = 0x00; |
| constexpr uint8_t bkcIndexValue = 0x01; |
| constexpr uint8_t tempIndexValue = 0x00; |
| try |
| { |
| SPIDev spiDev(mtdDevBuf); |
| spiDev.spiReadData(svnVerOffsetInPfm, bufLength, temp.data()); |
| fwSecurityVersionBuf.at(svnIndexValue) = temp.at(tempIndexValue); |
| spiDev.spiReadData(bkcVerOffsetInPfm, bufLength, temp.data()); |
| fwSecurityVersionBuf.at(bkcIndexValue) = temp.at(tempIndexValue); |
| } |
| catch (const std::exception& e) |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "Exception caught in getSecurityVersionInfo", |
| phosphor::logging::entry("MSG=%s", e.what())); |
| fwSecurityVersionBuf = {0, 0}; |
| } |
| |
| return fwSecurityVersionBuf; |
| } |
| |
| ipmi::RspType< |
| uint8_t, // device ID |
| uint8_t, // Active Image Value |
| std::array<uint8_t, imageCount>, // Security version for Active Image |
| uint8_t, // recovery Image Value |
| std::array<uint8_t, imageCount>> // Security version for Recovery Image |
| ipmiGetFwSecurityVersionInfo() |
| { |
| static bool cacheFlag = false; |
| constexpr std::array<const char*, imageCount> mtdDevBuf = { |
| bmcActivePfmMTDDev, bmcRecoveryImgMTDDev}; |
| |
| // To avoid multiple reading from SPI device |
| if (!cacheFlag) |
| { |
| imgFwSecurityVersion[0] = getSecurityVersionInfo( |
| mtdDevBuf[0], svnActiveVerOffsetInPfm, bkcActiveVerOffsetInPfm); |
| imgFwSecurityVersion[1] = getSecurityVersionInfo( |
| mtdDevBuf[1], svnRecoveryVerOffsetInPfm, bkcRecoveryVerOffsetInPfm); |
| cacheFlag = true; |
| } |
| |
| constexpr uint8_t ActivePfmMTDDev = 0x00; |
| constexpr uint8_t RecoveryImgMTDDev = 0x01; |
| |
| return ipmi::responseSuccess( |
| imageCount, static_cast<uint8_t>(FWDeviceIDTag::bmcActiveImage), |
| imgFwSecurityVersion[ActivePfmMTDDev], |
| static_cast<uint8_t>(FWDeviceIDTag::bmcRecoveryImage), |
| imgFwSecurityVersion[RecoveryImgMTDDev]); |
| } |
| |
| ipmi::RspType<std::array<uint8_t, certKeyLen>, |
| std::optional<std::array<uint8_t, cskSignatureLen>>> |
| ipmiGetFwRootCertData(const ipmi::Context::ptr& ctx, uint8_t certId) |
| { |
| bool isIPMBChannel = false; |
| |
| if (checkIPMBChannel(ctx, isIPMBChannel) != ipmi::ccSuccess) |
| { |
| return ipmi::responseUnspecifiedError(); |
| } |
| if (isIPMBChannel) |
| { |
| phosphor::logging::log<phosphor::logging::level::INFO>( |
| "Command not supported. Failed to get root certificate data."); |
| return ipmi::responseCommandNotAvailable(); |
| } |
| |
| size_t certKeyOffset = 0; |
| size_t cskSigOffset = 0; |
| std::string mtdDev; |
| |
| switch (static_cast<FwGetRootCertDataTag>(certId)) |
| { |
| case FwGetRootCertDataTag::activeRootKey: |
| { |
| mtdDev = bmcActivePfmMTDDev; |
| certKeyOffset = rootkeyOffsetInPfm; |
| break; |
| } |
| case FwGetRootCertDataTag::recoveryRootKey: |
| { |
| mtdDev = bmcRecoveryImgMTDDev; |
| certKeyOffset = pfmBaseOffsetInImage + rootkeyOffsetInPfm; |
| break; |
| } |
| case FwGetRootCertDataTag::activeCSK: |
| { |
| mtdDev = bmcActivePfmMTDDev; |
| certKeyOffset = cskKeyOffsetInPfm; |
| cskSigOffset = cskSignatureOffsetInPfm; |
| break; |
| } |
| case FwGetRootCertDataTag::recoveryCSK: |
| { |
| mtdDev = bmcRecoveryImgMTDDev; |
| certKeyOffset = pfmBaseOffsetInImage + cskKeyOffsetInPfm; |
| cskSigOffset = pfmBaseOffsetInImage + cskSignatureOffsetInPfm; |
| break; |
| } |
| default: |
| { |
| return ipmi::responseInvalidFieldRequest(); |
| } |
| } |
| |
| std::array<uint8_t, certKeyLen> certKey = {0}; |
| |
| try |
| { |
| SPIDev spiDev(mtdDev); |
| spiDev.spiReadData(certKeyOffset, certKeyLen, certKey.data()); |
| |
| if (cskSigOffset) |
| { |
| std::array<uint8_t, cskSignatureLen> cskSignature = {0}; |
| spiDev.spiReadData(cskSigOffset, cskSignatureLen, |
| cskSignature.data()); |
| return ipmi::responseSuccess(certKey, cskSignature); |
| } |
| } |
| catch (const std::exception& e) |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "Exception caught in ipmiGetFwRootCertData", |
| phosphor::logging::entry("MSG=%s", e.what())); |
| return ipmi::responseUnspecifiedError(); |
| } |
| |
| return ipmi::responseSuccess(certKey, std::nullopt); |
| } |
| #endif // INTEL_PFR_ENABLED |
| |
| static constexpr uint8_t channelListSize = 3; |
| /** @brief implements Maximum Firmware Transfer size command |
| * @parameter |
| * - none |
| * @returns IPMI completion code plus response data |
| * - count - channel count |
| * - channelList - channel list information |
| */ |
| ipmi::RspType<uint8_t, // channel count |
| std::array<std::tuple<uint8_t, uint32_t>, |
| channelListSize> // Channel List |
| > |
| ipmiFirmwareMaxTransferSize() |
| { |
| constexpr size_t kcsMaxBufSize = 128; |
| constexpr size_t rmcpPlusMaxBufSize = 50 * 1024; |
| // Byte 1 - Count (N) Number of devices data is being returned for. |
| // Byte 2 - ID Tag 00 – reserved 01 – kcs 02 – rmcp+, 03 - ipmb |
| // Byte 3-6 - transfer size (little endian) |
| // Bytes - 7:(5xN) - Repeat of 2 through 6 |
| constexpr std::array<std::tuple<uint8_t, uint32_t>, channelListSize> |
| channelList = { |
| {{static_cast<uint8_t>(ChannelIdTag::kcs), kcsMaxBufSize}, |
| {static_cast<uint8_t>(ChannelIdTag::rmcpPlus), |
| rmcpPlusMaxBufSize}}}; |
| |
| return ipmi::responseSuccess(channelListSize, channelList); |
| } |
| |
| ipmi::RspType<uint8_t, uint8_t> |
| ipmiGetBmcExecutionContext(ipmi::Context::ptr ctx) |
| { |
| // Byte 1 - Current execution context |
| // 0x10 - Linux OS, 0x11 - Bootloader, Forced-firmware updat mode |
| // Byte 2 - Partition pointer |
| // 0x01 - primary, 0x02 - secondary |
| uint8_t partitionPtr = getActiveBootImage(ctx); |
| |
| return ipmi::responseSuccess( |
| static_cast<uint8_t>(BmcExecutionContext::linuxOs), partitionPtr); |
| } |
| /** @brief Get Firmware Update Random Number |
| * |
| * This function generate the random number used for |
| * setting the firmware update mode as authentication key. |
| * |
| * @param[in] ctx - context of current session |
| * @returns IPMI completion code along with |
| * - random number |
| **/ |
| ipmi::RspType<std::array<uint8_t, fwRandomNumLength>> |
| ipmiGetFwUpdateRandomNumber(const ipmi::Context::ptr& ctx) |
| { |
| phosphor::logging::log<phosphor::logging::level::INFO>( |
| "Generate FW update random number"); |
| bool isIPMBChannel = false; |
| |
| if (checkIPMBChannel(ctx, isIPMBChannel) != ipmi::ccSuccess) |
| { |
| return ipmi::responseUnspecifiedError(); |
| } |
| if (isIPMBChannel) |
| { |
| phosphor::logging::log<phosphor::logging::level::INFO>( |
| "Channel not supported. Failed to fetch FW update random number"); |
| return ipmi::responseCommandNotAvailable(); |
| } |
| std::random_device rd; |
| std::default_random_engine gen(rd()); |
| std::uniform_int_distribution<> dist{0, 255}; |
| |
| fwRandomNumGenTs = std::chrono::steady_clock::now(); |
| |
| for (int i = 0; i < fwRandomNumLength; i++) |
| { |
| fwRandomNum[i] = dist(gen); |
| } |
| |
| return ipmi::responseSuccess(fwRandomNum); |
| } |
| |
| /** @brief Set Firmware Update Mode |
| * |
| * This function sets BMC into firmware update mode |
| * after validating Random number obtained from the Get |
| * Firmware Update Random Number command |
| * |
| * @param[in] ctx - context of current session |
| * @parameter randNum - Random number(token) |
| * @returns IPMI completion code |
| **/ |
| ipmi::RspType<> |
| ipmiSetFirmwareUpdateMode(const ipmi::Context::ptr& ctx, |
| std::array<uint8_t, fwRandomNumLength>& randNum) |
| { |
| phosphor::logging::log<phosphor::logging::level::INFO>( |
| "Start FW update mode"); |
| |
| bool isIPMBChannel = false; |
| |
| if (checkIPMBChannel(ctx, isIPMBChannel) != ipmi::ccSuccess) |
| { |
| return ipmi::responseUnspecifiedError(); |
| } |
| if (isIPMBChannel) |
| { |
| phosphor::logging::log<phosphor::logging::level::INFO>( |
| "Channel not supported. Failed to set FW update mode"); |
| return ipmi::responseCommandNotAvailable(); |
| } |
| /* Firmware Update Random number is valid for 30 seconds only */ |
| auto timeElapsed = (std::chrono::steady_clock::now() - fwRandomNumGenTs); |
| if (std::chrono::duration_cast<std::chrono::microseconds>(timeElapsed) |
| .count() > std::chrono::duration_cast<std::chrono::microseconds>( |
| fwRandomNumExpirySeconds) |
| .count()) |
| { |
| phosphor::logging::log<phosphor::logging::level::INFO>( |
| "Firmware update random number expired."); |
| return ipmi::responseInvalidFieldRequest(); |
| } |
| |
| /* Validate random number */ |
| for (int i = 0; i < fwRandomNumLength; i++) |
| { |
| if (fwRandomNum[i] != randNum[i]) |
| { |
| phosphor::logging::log<phosphor::logging::level::INFO>( |
| "Invalid random number specified."); |
| return ipmi::responseInvalidFieldRequest(); |
| } |
| } |
| |
| try |
| { |
| if (getFirmwareUpdateMode()) |
| { |
| phosphor::logging::log<phosphor::logging::level::INFO>( |
| "Already firmware update is in progress."); |
| return ipmi::responseBusy(); |
| } |
| } |
| catch (const std::exception& e) |
| { |
| return ipmi::responseUnspecifiedError(); |
| } |
| |
| // FIXME? c++ doesn't off an option for exclusive file creation |
| FILE* fp = fopen(firmwareBufferFile, "wx"); |
| if (!fp) |
| { |
| phosphor::logging::log<phosphor::logging::level::INFO>( |
| "Unable to open file."); |
| return ipmi::responseUnspecifiedError(); |
| } |
| fclose(fp); |
| |
| try |
| { |
| setFirmwareUpdateMode(true); |
| } |
| catch (const std::exception& e) |
| { |
| unlink(firmwareBufferFile); |
| return ipmi::responseUnspecifiedError(); |
| } |
| |
| return ipmi::responseSuccess(); |
| } |
| |
| /** @brief implements exit firmware update mode command |
| * @param None |
| * |
| * @returns IPMI completion code |
| */ |
| ipmi::RspType<> ipmiExitFirmwareUpdateMode(const ipmi::Context::ptr& ctx) |
| { |
| phosphor::logging::log<phosphor::logging::level::INFO>( |
| "Exit FW update mode"); |
| bool isIPMBChannel = false; |
| |
| if (checkIPMBChannel(ctx, isIPMBChannel) != ipmi::ccSuccess) |
| { |
| return ipmi::responseUnspecifiedError(); |
| } |
| if (isIPMBChannel) |
| { |
| phosphor::logging::log<phosphor::logging::level::INFO>( |
| "Command not supported. Failed to exit firmware update mode"); |
| return ipmi::responseCommandNotAvailable(); |
| } |
| |
| switch (fwUpdateStatus.getState()) |
| { |
| case FwUpdateStatusCache::fwStateInit: |
| case FwUpdateStatusCache::fwStateIdle: |
| return ipmi::responseInvalidFieldRequest(); |
| break; |
| case FwUpdateStatusCache::fwStateDownload: |
| case FwUpdateStatusCache::fwStateVerify: |
| break; |
| case FwUpdateStatusCache::fwStateProgram: |
| break; |
| case FwUpdateStatusCache::fwStateUpdateSuccess: |
| case FwUpdateStatusCache::fwStateError: |
| break; |
| case FwUpdateStatusCache::fwStateAcCycleRequired: |
| return ipmi::responseInvalidFieldRequest(); |
| break; |
| } |
| fwUpdateStatus.firmwareUpdateAbortState(); |
| |
| try |
| { |
| setFirmwareUpdateMode(false); |
| } |
| catch (const std::exception& e) |
| { |
| return ipmi::responseUnspecifiedError(); |
| } |
| |
| return ipmi::responseSuccess(); |
| } |
| |
| /** @brief implements Get/Set Firmware Update Control |
| * @param[in] ctx - context of current session |
| * @parameter |
| * - Byte 1: Control Byte |
| * - Byte 2: Firmware filename length (Optional) |
| * - Byte 3:N: Firmware filename data (Optional) |
| * @returns IPMI completion code plus response data |
| * - Byte 2: Current control status |
| **/ |
| ipmi::RspType<bool, bool, bool, bool, uint4_t> |
| ipmiGetSetFirmwareUpdateControl(const ipmi::Context::ptr& ctx, |
| const uint8_t controlReq, |
| const std::optional<std::string>& fileName) |
| { |
| bool isIPMBChannel = false; |
| |
| if (checkIPMBChannel(ctx, isIPMBChannel) != ipmi::ccSuccess) |
| { |
| return ipmi::responseUnspecifiedError(); |
| } |
| if (isIPMBChannel) |
| { |
| phosphor::logging::log<phosphor::logging::level::INFO>( |
| "Channel not supported. Failed to get or set FW update control"); |
| return ipmi::responseCommandNotAvailable(); |
| } |
| |
| static std::string fwXferUriPath; |
| static bool imageTransferStarted = false; |
| static bool imageTransferCompleted = false; |
| static bool imageTransferAborted = false; |
| |
| if ((controlReq != |
| static_cast<uint8_t>(FwUpdateCtrlReq::setFirmwareFilename)) && |
| (fileName)) |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "Invalid request field (Filename)."); |
| return ipmi::responseInvalidFieldRequest(); |
| } |
| |
| static bool usbAttached = getUsbStatus(); |
| |
| switch (static_cast<FwUpdateCtrlReq>(controlReq)) |
| { |
| case FwUpdateCtrlReq::getCurrentControlStatus: |
| phosphor::logging::log<phosphor::logging::level::INFO>( |
| "ipmiGetSetFirmwareUpdateControl: Get status"); |
| break; |
| case FwUpdateCtrlReq::imageTransferStart: |
| { |
| phosphor::logging::log<phosphor::logging::level::INFO>( |
| "ipmiGetSetFirmwareUpdateControl: Set transfer start"); |
| imageTransferStarted = true; |
| // reset buffer to empty (truncate file) |
| std::ofstream out(firmwareBufferFile, |
| std::ofstream::binary | std::ofstream::trunc); |
| fwXferUriPath = std::string("file://") + firmwareBufferFile; |
| if (xferHashCheck) |
| { |
| if (!xferHashCheck->clear()) |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "clear() for xferHashCheck failed"); |
| return ipmi::responseUnspecifiedError(); |
| } |
| } |
| // Setting state to download |
| fwUpdateStatus.setState( |
| static_cast<uint8_t>(FwUpdateStatusCache::fwStateDownload)); |
| #ifdef INTEL_PFR_ENABLED |
| imgLength = 0; |
| imgType = 0; |
| block0Mapped = false; |
| #endif |
| } |
| break; |
| case FwUpdateCtrlReq::imageTransferComplete: |
| { |
| phosphor::logging::log<phosphor::logging::level::INFO>( |
| "ipmiGetSetFirmwareUpdateControl: Set transfer complete."); |
| if (usbAttached) |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "USB should be detached to perform this operation."); |
| return ipmi::responseNotSupportedInPresentState(); |
| } |
| // finish transfer based on URI |
| if (!transferFirmwareFromUri(fwXferUriPath)) |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "transferFirmwareFromUri failed."); |
| return ipmi::responseUnspecifiedError(); |
| } |
| // transfer complete |
| if (xferHashCheck) |
| { |
| if (TransferHashCheck::HashCheck::sha2Success != |
| xferHashCheck->verify()) |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "xferHashCheck failed."); |
| return ipmi::responseUnspecifiedError(); |
| } |
| } |
| // Set state to verify and start the update |
| fwUpdateStatus.setState( |
| static_cast<uint8_t>(FwUpdateStatusCache::fwStateVerify)); |
| // start the request |
| if (!startFirmwareUpdate(firmwareBufferFile)) |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "startFirmwareUpdate failed."); |
| return ipmi::responseUnspecifiedError(); |
| } |
| imageTransferCompleted = true; |
| } |
| break; |
| case FwUpdateCtrlReq::imageTransferAbort: |
| phosphor::logging::log<phosphor::logging::level::INFO>( |
| "ipmiGetSetFirmwareUpdateControl: Set transfer abort."); |
| if (usbAttached) |
| { |
| if (detachUsbDevice()) |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "Detach USB device failed."); |
| return ipmi::responseUsbAttachOrDetachFailed(); |
| } |
| usbAttached = false; |
| } |
| // During abort request reset the state to Init by cleaning update |
| // file. |
| fwUpdateStatus.firmwareUpdateAbortState(); |
| imageTransferAborted = true; |
| break; |
| case FwUpdateCtrlReq::setFirmwareFilename: |
| phosphor::logging::log<phosphor::logging::level::INFO>( |
| "ipmiGetSetFirmwareUpdateControl: Set filename."); |
| if (!fileName || ((*fileName).length() == 0)) |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "Invalid Filename specified."); |
| return ipmi::responseInvalidFieldRequest(); |
| } |
| |
| fwXferUriPath = *fileName; |
| break; |
| case FwUpdateCtrlReq::attachUsbDevice: |
| phosphor::logging::log<phosphor::logging::level::INFO>( |
| "ipmiGetSetFirmwareUpdateControl: Attach USB device."); |
| if (usbAttached) |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "USB device is already attached."); |
| return ipmi::responseInvalidFieldRequest(); |
| } |
| if (attachUsbDevice()) |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "Attach USB device failed."); |
| return ipmi::responseUsbAttachOrDetachFailed(); |
| } |
| usbAttached = true; |
| break; |
| case FwUpdateCtrlReq::detachUsbDevice: |
| phosphor::logging::log<phosphor::logging::level::INFO>( |
| "ipmiGetSetFirmwareUpdateControl: Detach USB device."); |
| if (!usbAttached) |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "USB device is not attached."); |
| return ipmi::responseInvalidFieldRequest(); |
| } |
| if (detachUsbDevice()) |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "Detach USB device failed."); |
| return ipmi::responseUsbAttachOrDetachFailed(); |
| } |
| usbAttached = false; |
| break; |
| default: |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "Invalid control option specified."); |
| return ipmi::responseInvalidFieldRequest(); |
| } |
| |
| return ipmi::responseSuccess(imageTransferStarted, imageTransferCompleted, |
| imageTransferAborted, usbAttached, uint4_t(0)); |
| } |
| |
| /** @brief implements firmware get status command |
| * @parameter |
| * - none |
| * @returns IPMI completion code plus response data |
| * - status - processing status |
| * - percentage - percentage completion |
| * - check - channel integrity check status |
| **/ |
| ipmi::RspType<uint8_t, // status |
| uint8_t, // percentage |
| uint8_t // check |
| > |
| ipmiGetFirmwareUpdateStatus() |
| |
| { |
| // Byte 1 - status (0=init, 1=idle, 2=download, 3=validate, 4=write, |
| // 5=ready, f=error, 83=ac cycle required) |
| // Byte 2 - percent |
| // Byte 3 - integrity check status (0=none, 1=req, 2=sha2ok, e2=sha2fail) |
| uint8_t status = fwUpdateStatus.getState(); |
| uint8_t percent = fwUpdateStatus.percent(); |
| uint8_t check = xferHashCheck ? xferHashCheck->status() : 0; |
| |
| // Status code. |
| return ipmi::responseSuccess(status, percent, check); |
| } |
| |
| ipmi::RspType<bool, bool, bool, uint5_t> ipmiSetFirmwareUpdateOptions( |
| const ipmi::Context::ptr& ctx, bool noDowngradeMask, bool deferRestartMask, |
| bool sha2CheckMask, uint5_t reserved1, bool noDowngrade, bool deferRestart, |
| bool sha2Check, uint5_t reserved2, |
| std::optional<std::vector<uint8_t>> integrityCheckVal) |
| { |
| phosphor::logging::log<phosphor::logging::level::INFO>( |
| "Set firmware update options."); |
| bool isIPMBChannel = false; |
| |
| if (checkIPMBChannel(ctx, isIPMBChannel) != ipmi::ccSuccess) |
| { |
| return ipmi::responseUnspecifiedError(); |
| } |
| if (isIPMBChannel) |
| { |
| phosphor::logging::log<phosphor::logging::level::INFO>( |
| "Channel not supported. Failed to set firmware update options"); |
| return ipmi::responseCommandNotAvailable(); |
| } |
| bool noDowngradeState = fwUpdateStatus.getInhibitDowngrade(); |
| bool deferRestartState = fwUpdateStatus.getDeferRestart(); |
| bool sha2CheckState = xferHashCheck ? true : false; |
| |
| if (noDowngradeMask && (noDowngradeState != noDowngrade)) |
| { |
| fwUpdateStatus.setInhibitDowngrade(noDowngrade); |
| noDowngradeState = noDowngrade; |
| } |
| if (deferRestartMask && (deferRestartState != deferRestart)) |
| { |
| fwUpdateStatus.setDeferRestart(deferRestart); |
| deferRestartState = deferRestart; |
| } |
| if (sha2CheckMask) |
| { |
| if (sha2Check) |
| { |
| auto hashSize = EVP_MD_size(EVP_sha256()); |
| if ((*integrityCheckVal).size() != hashSize) |
| { |
| phosphor::logging::log<phosphor::logging::level::DEBUG>( |
| "Invalid size of Hash specified."); |
| return ipmi::responseInvalidFieldRequest(); |
| } |
| |
| try |
| { |
| xferHashCheck = |
| std::make_unique<TransferHashCheck>(*integrityCheckVal); |
| } |
| catch (std::exception& ex) |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| ex.what()); |
| return ipmi::responseUnspecifiedError(); |
| } |
| } |
| else |
| { |
| // delete the xferHashCheck object |
| xferHashCheck.reset(); |
| } |
| sha2CheckState = sha2CheckMask; |
| } |
| return ipmi::responseSuccess(noDowngradeState, deferRestartState, |
| sha2CheckState, reserved1); |
| } |
| |
| ipmi::RspType<uint32_t> |
| ipmiFwImageWriteData(const std::vector<uint8_t>& writeData) |
| { |
| const uint8_t ccCmdNotSupportedInPresentState = 0xD5; |
| size_t writeDataLen = writeData.size(); |
| |
| if (!writeDataLen) |
| { |
| return ipmi::responseReqDataLenInvalid(); |
| } |
| |
| if (fwUpdateStatus.getState() != FwUpdateStatusCache::fwStateDownload) |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "Invalid firmware update state."); |
| return ipmi::response(ccCmdNotSupportedInPresentState); |
| } |
| |
| std::ofstream out(firmwareBufferFile, |
| std::ofstream::binary | std::ofstream::app); |
| if (!out) |
| { |
| phosphor::logging::log<phosphor::logging::level::DEBUG>( |
| "Error while opening file."); |
| return ipmi::responseUnspecifiedError(); |
| } |
| |
| uint64_t fileDataLen = out.tellp(); |
| |
| if ((fileDataLen + writeDataLen) > maxFirmwareImageSize) |
| { |
| phosphor::logging::log<phosphor::logging::level::DEBUG>( |
| "Firmware image size exceeds the limit"); |
| return ipmi::responseInvalidFieldRequest(); |
| } |
| |
| const char* data = reinterpret_cast<const char*>(writeData.data()); |
| out.write(data, writeDataLen); |
| out.close(); |
| |
| if (xferHashCheck) |
| { |
| if (!xferHashCheck->hash(writeData)) |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| "ipmiFwImageWriteData: xferHashCheck->hash failed."); |
| return ipmi::responseUnspecifiedError(); |
| } |
| } |
| |
| #ifdef INTEL_PFR_ENABLED |
| /* PFR image block 0 - As defined in HAS */ |
| struct PFRImageBlock0 |
| { |
| uint32_t tag; |
| uint32_t pcLength; |
| uint32_t pcType; |
| uint32_t reserved1; |
| uint8_t hash256[32]; |
| uint8_t hash384[48]; |
| uint8_t reserved2[32]; |
| } __attribute__((packed)); |
| |
| /* Get the PFR block 0 data and read the uploaded image |
| * information( Image type, length etc) */ |
| if (((fileDataLen + writeDataLen) >= sizeof(PFRImageBlock0)) && |
| (!block0Mapped)) |
| { |
| struct PFRImageBlock0 block0Data = {0}; |
| |
| std::ifstream inFile(firmwareBufferFile, |
| std::ios::binary | std::ios::in); |
| inFile.read(reinterpret_cast<char*>(&block0Data), sizeof(block0Data)); |
| inFile.close(); |
| |
| uint32_t magicNum = block0Data.tag; |
| |
| /* Validate the magic number */ |
| if (magicNum != perBlock0MagicNum) |
| { |
| phosphor::logging::log<phosphor::logging::level::DEBUG>( |
| "PFR image magic number not matched"); |
| return ipmi::responseInvalidFieldRequest(); |
| } |
| // Note:imgLength, imgType and block0Mapped are in global scope, as |
| // these are used in cascaded updates. |
| imgLength = block0Data.pcLength; |
| imgType = block0Data.pcType; |
| block0Mapped = true; |
| } |
| #endif // end of INTEL_PFR_ENABLED |
| return ipmi::responseSuccess(writeDataLen); |
| } |
| |
| static void registerFirmwareFunctions() |
| { |
| // guarantee that we start with an already timed out timestamp |
| fwRandomNumGenTs = |
| std::chrono::steady_clock::now() - fwRandomNumExpirySeconds; |
| fwUpdateStatus.setState( |
| static_cast<uint8_t>(FwUpdateStatusCache::fwStateInit)); |
| |
| unlink(firmwareBufferFile); |
| |
| #ifdef INTEL_PFR_ENABLED |
| // Following commands are supported only for PFR enabled platforms |
| // CMD:0x20 - Get Firmware Version Information |
| // CMD:0x21 - Get Firmware Security Version Information |
| // CMD:0x25 - Get Root Certificate Data |
| |
| // get firmware version information |
| ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnFirmware, |
| ipmi::firmware::cmdGetFwVersionInfo, |
| ipmi::Privilege::Admin, ipmiGetFwVersionInfo); |
| |
| // get firmware security version information |
| ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnFirmware, |
| ipmi::firmware::cmdGetFwSecurityVersionInfo, |
| ipmi::Privilege::Admin, ipmiGetFwSecurityVersionInfo); |
| |
| // get root certificate data |
| ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnFirmware, |
| ipmi::firmware::cmdFwGetRootCertData, |
| ipmi::Privilege::Admin, ipmiGetFwRootCertData); |
| #endif |
| |
| // get firmware update channel information (max transfer sizes) |
| ipmi::registerHandler(ipmi::prioOemBase, ipmi::netFnFirmware, |
| ipmi::firmware::cmdGetFwUpdateChannelInfo, |
| ipmi::Privilege::Admin, ipmiFirmwareMaxTransferSize); |
| |
| // get bmc execution context |
| ipmi::registerHandler(ipmi::prioOemBase, ipmi::netFnFirmware, |
| ipmi::firmware::cmdGetBmcExecutionContext, |
| ipmi::Privilege::Admin, ipmiGetBmcExecutionContext); |
| |
| // Get Firmware Update Random number |
| ipmi::registerHandler(ipmi::prioOemBase, ipmi::netFnFirmware, |
| ipmi::firmware::cmdGetFwUpdateRandomNumber, |
| ipmi::Privilege::Admin, ipmiGetFwUpdateRandomNumber); |
| |
| // Set Firmware Update Mode |
| ipmi::registerHandler(ipmi::prioOemBase, ipmi::netFnFirmware, |
| ipmi::firmware::cmdSetFirmwareUpdateMode, |
| ipmi::Privilege::Admin, ipmiSetFirmwareUpdateMode); |
| |
| // Exit Firmware Update Mode |
| ipmi::registerHandler(ipmi::prioOemBase, ipmi::netFnFirmware, |
| ipmi::firmware::cmdExitFirmwareUpdateMode, |
| ipmi::Privilege::Admin, ipmiExitFirmwareUpdateMode); |
| |
| // Get/Set Firmware Update Control |
| ipmi::registerHandler(ipmi::prioOemBase, ipmi::netFnFirmware, |
| ipmi::firmware::cmdGetSetFwUpdateControl, |
| ipmi::Privilege::Admin, |
| ipmiGetSetFirmwareUpdateControl); |
| |
| // Get Firmware Update Status |
| ipmi::registerHandler(ipmi::prioOemBase, ipmi::netFnFirmware, |
| ipmi::firmware::cmdGetFirmwareUpdateStatus, |
| ipmi::Privilege::Admin, ipmiGetFirmwareUpdateStatus); |
| |
| // Set Firmware Update Options |
| ipmi::registerHandler(ipmi::prioOemBase, ipmi::netFnFirmware, |
| ipmi::firmware::cmdSetFirmwareUpdateOptions, |
| ipmi::Privilege::Admin, ipmiSetFirmwareUpdateOptions); |
| // write image data |
| ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnFirmware, |
| ipmi::firmware::cmdFwImageWriteData, |
| ipmi::Privilege::Admin, ipmiFwImageWriteData); |
| return; |
| } |