#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef INTEL_PFR_ENABLED #include #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, imageCount> imgFwSecurityVersion = {}; 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 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 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 = 33_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> ctx; std::vector expectedHash; HashCheck check; public: TransferHashCheck(const std::vector& 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& 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 digest(EVP_MD_size(EVP_sha256())); check = HashCheck::sha2Failed; if (EVP_DigestFinal(ctx.get(), digest.data(), &len) == openSslSuccess) { if (digest == expectedHash) { phosphor::logging::log( "Transfer sha2 verify passed."); check = HashCheck::sha2Success; } else { phosphor::logging::log( "Transfer sha2 verify failed."); check = HashCheck::sha2Failed; } } return check; } uint8_t status() const { return static_cast(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(addr); } size_t size() const { return fsize; } private: void* addr; size_t fsize; }; 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 busp = getSdBus(); fwUpdateState = fwStateProgram; progressPercent = 0; match = std::make_shared( *busp, sdbusplus::bus::match::rules::propertiesChanged( objPath, "xyz.openbmc_project.Software.ActivationProgress"), [&](sdbusplus::message_t& msg) { std::map props; std::vector inVal; std::string iface; try { msg.read(iface, props, inVal); } catch (const std::exception& e) { phosphor::logging::log( "Exception caught in get ActivationProgress"); return; } auto it = props.find("Progress"); if (it != props.end()) { progressPercent = std::get(it->second); if (progressPercent == 100) { fwUpdateState = fwStateUpdateSuccess; } } }); } uint8_t activationTimerTimeout() { phosphor::logging::log( "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 busp; std::shared_ptr match; uint8_t fwUpdateState = 0; uint8_t progressPercent = 0; bool deferRestartState = false; bool inhibitDowngradeState = false; }; static FwUpdateStatusCache fwUpdateStatus; std::unique_ptr xferHashCheck; static void activateImage(const std::string& objPath) { // If flag is false means to reboot if (fwUpdateStatus.getDeferRestart() == false) { phosphor::logging::log( "activating Image: ", phosphor::logging::entry("OBJPATH =%s", objPath.c_str())); std::shared_ptr bus = getSdBus(); bus->async_method_call( [](const boost::system::error_code ec) { if (ec) { phosphor::logging::log( "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", ipmi::DbusVariant("xyz.openbmc_project.Software.Activation." "RequestedActivations.Active")); } else { phosphor::logging::log( "Firmware image activation is deferred."); } fwUpdateStatus.setState( static_cast(FwUpdateStatusCache::fwStateUpdateSuccess)); } static bool getFirmwareUpdateMode() { std::shared_ptr 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(state); return (bmcState == bmcStateUpdateInProgress); } catch (const std::exception& e) { phosphor::logging::log( "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 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( "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( "Failed to get Channel Info", phosphor::logging::entry("CHANNEL=%d", ctx->channel)); return ipmi::ccUnspecifiedError; } if (static_cast(chInfo.mediumType) == ipmi::EChannelMediumType::ipmb) { isIPMBChannel = true; } return ipmi::ccSuccess; } static void postTransferCompleteHandler( std::unique_ptr& fwUpdateMatchSignal) { // Setup timer for watching signal static sdbusplus::Timer timer([&fwUpdateMatchSignal]() { fwUpdateMatchSignal = nullptr; }); static sdbusplus::Timer activationStatusTimer([]() { if (fwUpdateStatus.activationTimerTimeout() > 95) { activationStatusTimer.stop(); fwUpdateStatus.setState( static_cast(FwUpdateStatusCache::fwStateVerify)); } }); timer.start(std::chrono::microseconds(5000000), false); // callback function for capturing signal auto callback = [&](sdbusplus::message_t& m) { std::vector< std::pair>>> 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( "Exception caught in reading created object path."); return; } // constructing response message phosphor::logging::log( "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( "Start activationStatusTimer for status."); try { timer.stop(); activationStatusTimer.start( std::chrono::microseconds(3000000), true); } catch (const std::exception& e) { phosphor::logging::log( "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( *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 fwUpdateMatchSignal; postTransferCompleteHandler(fwUpdateMatchSignal); std::string randomFname = "/tmp/images/" + boost::uuids::to_string(boost::uuids::random_generator()()); std::error_code fsError{}; std::filesystem::rename(uri, randomFname, fsError); if (fsError) { // The source and destination may not be in the same // filesystem. std::filesystem::copy(uri, randomFname, fsError); if (fsError) { phosphor::logging::log( "Unable to move/copy image to destination directory.", phosphor::logging::entry("ERROR=%s", fsError.message().c_str())); return false; } std::filesystem::remove(uri); } return true; } static bool transferImageFromFile(const std::string& uri, bool move = true) { std::error_code ec; phosphor::logging::log( "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( "transferImageFromFile: xferHashCheck->hash failed."); return false; } } if (ec.value()) { phosphor::logging::log( "Image copy failed."); return false; } return true; } template static int executeCmd(const char* path, ArgTypes&&... tArgs) { boost::process::child execProg(path, const_cast(tArgs)...); execProg.wait(); return execProg.exit_code(); } static bool transferImageFromUsb(const std::string& uri) { bool ret = false; phosphor::logging::log( "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( "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( 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( "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; // Update Timestamp ipmi::RspType> 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 fwVerInfoList; std::shared_ptr 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(result); } catch (const std::exception& e) { phosphor::logging::log( "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( "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 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 splitVer; boost::split(splitVer, verStr, boost::is_any_of(".-")); if (splitVer.size() < 3) { phosphor::logging::log( "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(fwDev.first), majorNum, minorNum, buildNum, 0, 0)); } catch (const std::exception& e) { phosphor::logging::log( "Failed to convert stoul.", phosphor::logging::entry("ERROR=%s", e.what())); continue; } } return ipmi::responseSuccess(fwVerInfoList.size(), fwVerInfoList); } std::array getSecurityVersionInfo( const char* mtdDevBuf, size_t svnVerOffsetInPfm, size_t bkcVerOffsetInPfm) { constexpr size_t bufLength = 1; std::array 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( "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, // Security version for Active Image uint8_t, // recovery Image Value std::array> // Security version for Recovery Image ipmiGetFwSecurityVersionInfo() { static bool cacheFlag = false; constexpr std::array 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(FWDeviceIDTag::bmcActiveImage), imgFwSecurityVersion[ActivePfmMTDDev], static_cast(FWDeviceIDTag::bmcRecoveryImage), imgFwSecurityVersion[RecoveryImgMTDDev]); } ipmi::RspType, std::optional>> 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( "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(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 certKey = {0}; try { SPIDev spiDev(mtdDev); spiDev.spiReadData(certKeyOffset, certKeyLen, certKey.data()); if (cskSigOffset) { std::array cskSignature = {0}; spiDev.spiReadData(cskSigOffset, cskSignatureLen, cskSignature.data()); return ipmi::responseSuccess(certKey, cskSignature); } } catch (const std::exception& e) { phosphor::logging::log( "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, 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, channelListSize> channelList = { {{static_cast(ChannelIdTag::kcs), kcsMaxBufSize}, {static_cast(ChannelIdTag::rmcpPlus), rmcpPlusMaxBufSize}}}; return ipmi::responseSuccess(channelListSize, channelList); } ipmi::RspType 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(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> ipmiGetFwUpdateRandomNumber(const ipmi::Context::ptr& ctx) { phosphor::logging::log( "Generate FW update random number"); bool isIPMBChannel = false; if (checkIPMBChannel(ctx, isIPMBChannel) != ipmi::ccSuccess) { return ipmi::responseUnspecifiedError(); } if (isIPMBChannel) { phosphor::logging::log( "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 (size_t 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& randNum) { phosphor::logging::log( "Start FW update mode"); bool isIPMBChannel = false; if (checkIPMBChannel(ctx, isIPMBChannel) != ipmi::ccSuccess) { return ipmi::responseUnspecifiedError(); } if (isIPMBChannel) { phosphor::logging::log( "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(timeElapsed) .count() > std::chrono::duration_cast( fwRandomNumExpirySeconds) .count()) { phosphor::logging::log( "Firmware update random number expired."); return ipmi::responseInvalidFieldRequest(); } /* Validate random number */ for (size_t i = 0; i < fwRandomNumLength; i++) { if (fwRandomNum[i] != randNum[i]) { phosphor::logging::log( "Invalid random number specified."); return ipmi::responseInvalidFieldRequest(); } } try { if (getFirmwareUpdateMode()) { phosphor::logging::log( "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( "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( "Exit FW update mode"); bool isIPMBChannel = false; if (checkIPMBChannel(ctx, isIPMBChannel) != ipmi::ccSuccess) { return ipmi::responseUnspecifiedError(); } if (isIPMBChannel) { phosphor::logging::log( "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 ipmiGetSetFirmwareUpdateControl( const ipmi::Context::ptr& ctx, const uint8_t controlReq, const std::optional& fileName) { bool isIPMBChannel = false; if (checkIPMBChannel(ctx, isIPMBChannel) != ipmi::ccSuccess) { return ipmi::responseUnspecifiedError(); } if (isIPMBChannel) { phosphor::logging::log( "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(FwUpdateCtrlReq::setFirmwareFilename)) && (fileName)) { phosphor::logging::log( "Invalid request field (Filename)."); return ipmi::responseInvalidFieldRequest(); } static bool usbAttached = getUsbStatus(); switch (static_cast(controlReq)) { case FwUpdateCtrlReq::getCurrentControlStatus: phosphor::logging::log( "ipmiGetSetFirmwareUpdateControl: Get status"); break; case FwUpdateCtrlReq::imageTransferStart: { phosphor::logging::log( "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( "clear() for xferHashCheck failed"); return ipmi::responseUnspecifiedError(); } } // Setting state to download fwUpdateStatus.setState( static_cast(FwUpdateStatusCache::fwStateDownload)); #ifdef INTEL_PFR_ENABLED imgLength = 0; imgType = 0; block0Mapped = false; #endif } break; case FwUpdateCtrlReq::imageTransferComplete: { if (!imageTransferStarted) { phosphor::logging::log( "transferFirmwareUpdate not started."); return ipmi::responseNotSupportedInPresentState(); } phosphor::logging::log( "ipmiGetSetFirmwareUpdateControl: Set transfer complete."); if (usbAttached) { phosphor::logging::log( "USB should be detached to perform this operation."); return ipmi::responseNotSupportedInPresentState(); } // finish transfer based on URI if (!transferFirmwareFromUri(fwXferUriPath)) { phosphor::logging::log( "transferFirmwareFromUri failed."); return ipmi::responseUnspecifiedError(); } // transfer complete if (xferHashCheck) { if (TransferHashCheck::HashCheck::sha2Success != xferHashCheck->verify()) { phosphor::logging::log( "xferHashCheck failed."); return ipmi::responseUnspecifiedError(); } } // Set state to verify and start the update fwUpdateStatus.setState( static_cast(FwUpdateStatusCache::fwStateVerify)); // start the request if (!startFirmwareUpdate(firmwareBufferFile)) { phosphor::logging::log( "startFirmwareUpdate failed."); return ipmi::responseUnspecifiedError(); } imageTransferCompleted = true; } break; case FwUpdateCtrlReq::imageTransferAbort: phosphor::logging::log( "ipmiGetSetFirmwareUpdateControl: Set transfer abort."); if (usbAttached) { if (detachUsbDevice()) { phosphor::logging::log( "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( "ipmiGetSetFirmwareUpdateControl: Set filename."); if (!fileName || ((*fileName).length() == 0)) { phosphor::logging::log( "Invalid Filename specified."); return ipmi::responseInvalidFieldRequest(); } fwXferUriPath = *fileName; break; case FwUpdateCtrlReq::attachUsbDevice: phosphor::logging::log( "ipmiGetSetFirmwareUpdateControl: Attach USB device."); if (usbAttached) { phosphor::logging::log( "USB device is already attached."); return ipmi::responseInvalidFieldRequest(); } if (attachUsbDevice()) { phosphor::logging::log( "Attach USB device failed."); return ipmi::responseUsbAttachOrDetachFailed(); } usbAttached = true; break; case FwUpdateCtrlReq::detachUsbDevice: phosphor::logging::log( "ipmiGetSetFirmwareUpdateControl: Detach USB device."); if (!usbAttached) { phosphor::logging::log( "USB device is not attached."); return ipmi::responseInvalidFieldRequest(); } if (detachUsbDevice()) { phosphor::logging::log( "Detach USB device failed."); return ipmi::responseUsbAttachOrDetachFailed(); } usbAttached = false; break; default: phosphor::logging::log( "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 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 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> integrityCheckVal) { phosphor::logging::log( "Set firmware update options."); bool isIPMBChannel = false; if (reserved1 || reserved2) { return ipmi::responseInvalidFieldRequest(); } if (checkIPMBChannel(ctx, isIPMBChannel) != ipmi::ccSuccess) { return ipmi::responseUnspecifiedError(); } if (isIPMBChannel) { phosphor::logging::log( "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) { size_t hashSize = EVP_MD_size(EVP_sha256()); if ((*integrityCheckVal).size() != hashSize) { phosphor::logging::log( "Invalid size of Hash specified."); return ipmi::responseInvalidFieldRequest(); } try { xferHashCheck = std::make_unique(*integrityCheckVal); } catch (const std::exception& ex) { phosphor::logging::log( ex.what()); return ipmi::responseUnspecifiedError(); } } else { // delete the xferHashCheck object xferHashCheck.reset(); } sha2CheckState = sha2CheckMask; } return ipmi::responseSuccess(noDowngradeState, deferRestartState, sha2CheckState, uint5_t{}); } ipmi::RspType ipmiFwImageWriteData(const std::vector& writeData) { const uint8_t ccCmdNotSupportedInPresentState = 0xD5; size_t writeDataLen = writeData.size(); if (!writeDataLen) { return ipmi::responseReqDataLenInvalid(); } if (fwUpdateStatus.getState() != FwUpdateStatusCache::fwStateDownload) { phosphor::logging::log( "Invalid firmware update state."); return ipmi::response(ccCmdNotSupportedInPresentState); } std::ofstream out(firmwareBufferFile, std::ofstream::binary | std::ofstream::app); if (!out) { phosphor::logging::log( "Error while opening file."); return ipmi::responseUnspecifiedError(); } uint64_t fileDataLen = out.tellp(); if ((fileDataLen + writeDataLen) > maxFirmwareImageSize) { phosphor::logging::log( "Firmware image size exceeds the limit"); return ipmi::responseInvalidFieldRequest(); } const char* data = reinterpret_cast(writeData.data()); out.write(data, writeDataLen); out.close(); if (xferHashCheck) { if (!xferHashCheck->hash(writeData)) { phosphor::logging::log( "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)) { PFRImageBlock0 block0Data{}; std::ifstream inFile(firmwareBufferFile, std::ios::binary | std::ios::in); inFile.read(reinterpret_cast(&block0Data), sizeof(block0Data)); inFile.close(); uint32_t magicNum = block0Data.tag; /* Validate the magic number */ if (magicNum != perBlock0MagicNum) { phosphor::logging::log( "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(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; }