blob: 7e8f82f4a8201a6fd5ad53aba06e7a0af0c6abf8 [file] [log] [blame]
#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 =
{};
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:
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<sdbusplus::asio::connection> busp = getSdBus();
fwUpdateState = fwStateProgram;
progressPercent = 0;
match = std::make_shared<sdbusplus::bus::match_t>(
*busp,
sdbusplus::bus::match::rules::propertiesChanged(
objPath, "xyz.openbmc_project.Software.ActivationProgress"),
[&](sdbusplus::message_t& 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_t> 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",
ipmi::DbusVariant("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_t>& 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_t& m) {
std::vector<
std::pair<std::string,
std::vector<std::pair<std::string, ipmi::DbusVariant>>>>
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_t>(
*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_t> 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<phosphor::logging::level::ERR>(
"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<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;
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 (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<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 (size_t 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:
{
if (!imageTransferStarted)
{
phosphor::logging::log<phosphor::logging::level::ERR>(
"transferFirmwareUpdate not started.");
return ipmi::responseNotSupportedInPresentState();
}
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 (reserved1 || reserved2)
{
return ipmi::responseInvalidFieldRequest();
}
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)
{
size_t 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 (const 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, uint5_t{});
}
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))
{
PFRImageBlock0 block0Data{};
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;
}