/* // Copyright (c) 2018 Intel Corporation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. */ #include "types.hpp" #include "xyz/openbmc_project/Common/error.hpp" #include "xyz/openbmc_project/Led/Physical/server.hpp" #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 #include namespace ipmi { static void registerOEMFunctions() __attribute__((constructor)); static constexpr size_t maxFRUStringLength = 0x3F; static constexpr auto ethernetIntf = "xyz.openbmc_project.Network.EthernetInterface"; static constexpr auto networkIPIntf = "xyz.openbmc_project.Network.IP"; static constexpr auto networkService = "xyz.openbmc_project.Network"; static constexpr auto networkRoot = "/xyz/openbmc_project/network"; static constexpr const char* oemNmiSourceIntf = "xyz.openbmc_project.Chassis.Control.NMISource"; static constexpr const char* oemNmiSourceObjPath = "/xyz/openbmc_project/Chassis/Control/NMISource"; static constexpr const char* oemNmiBmcSourceObjPathProp = "BMCSource"; static constexpr const char* oemNmiEnabledObjPathProp = "Enabled"; static constexpr const char* dimmOffsetFile = "/var/lib/ipmi/ipmi_dimms.json"; static constexpr const char* multiNodeObjPath = "/xyz/openbmc_project/MultiNode/Status"; static constexpr const char* multiNodeIntf = "xyz.openbmc_project.Chassis.MultiNode"; enum class NmiSource : uint8_t { none = 0, frontPanelButton = 1, watchdog = 2, chassisCmd = 3, memoryError = 4, pciBusError = 5, pch = 6, chipset = 7, }; enum class SpecialUserIndex : uint8_t { rootUser = 0, atScaleDebugUser = 1 }; static constexpr const char* restricionModeService = "xyz.openbmc_project.RestrictionMode.Manager"; static constexpr const char* restricionModeBasePath = "/xyz/openbmc_project/control/security/restriction_mode"; static constexpr const char* restricionModeIntf = "xyz.openbmc_project.Control.Security.RestrictionMode"; static constexpr const char* restricionModeProperty = "RestrictionMode"; static constexpr const char* specialModeService = "xyz.openbmc_project.SpecialMode"; static constexpr const char* specialModeBasePath = "/xyz/openbmc_project/security/special_mode"; static constexpr const char* specialModeIntf = "xyz.openbmc_project.Security.SpecialMode"; static constexpr const char* specialModeProperty = "SpecialMode"; static constexpr const char* dBusPropertyIntf = "org.freedesktop.DBus.Properties"; static constexpr const char* dBusPropertyGetMethod = "Get"; static constexpr const char* dBusPropertySetMethod = "Set"; // return code: 0 successful int8_t getChassisSerialNumber(sdbusplus::bus_t& bus, std::string& serial) { std::string objpath = "/xyz/openbmc_project/FruDevice"; std::string intf = "xyz.openbmc_project.FruDeviceManager"; std::string service = getService(bus, intf, objpath); ObjectValueTree valueTree = getManagedObjects(bus, service, "/"); if (valueTree.empty()) { phosphor::logging::log( "No object implements interface", phosphor::logging::entry("INTF=%s", intf.c_str())); return -1; } for (const auto& item : valueTree) { auto interface = item.second.find("xyz.openbmc_project.FruDevice"); if (interface == item.second.end()) { continue; } auto property = interface->second.find("CHASSIS_SERIAL_NUMBER"); if (property == interface->second.end()) { continue; } try { Value variant = property->second; std::string& result = std::get(variant); if (result.size() > maxFRUStringLength) { phosphor::logging::log( "FRU serial number exceed maximum length"); return -1; } serial = result; return 0; } catch (const std::bad_variant_access& e) { phosphor::logging::log(e.what()); return -1; } } return -1; } namespace mailbox { static uint8_t bus = 4; static std::string i2cBus = "/dev/i2c-" + std::to_string(bus); static uint8_t targetAddr = 56; static constexpr auto systemRoot = "/xyz/openbmc_project/inventory/system"; static constexpr auto sessionIntf = "xyz.openbmc_project.Configuration.PFR"; const std::string match = "Baseboard/PFR"; static bool i2cConfigLoaded = false; // Command register for UFM provisioning/access commands; read/write allowed // from CPU/BMC. static const constexpr uint8_t provisioningCommand = 0x0b; // Trigger register for the command set in the previous offset. static const constexpr uint8_t triggerCommand = 0x0c; // Set 0x0c to 0x05 to execute command specified at “UFM/Provisioning Command” // register static const constexpr uint8_t flushRead = 0x05; // FIFO read registers std::set readFifoReg = {0x08, 0x0C, 0x0D, 0x13}; // UFM Read FIFO static const constexpr uint8_t readFifo = 0x0e; enum registerType : uint8_t { singleByteRegister = 0, fifoReadRegister, }; void loadPfrConfig(ipmi::Context::ptr& ctx, bool& i2cConfigLoaded) { ipmi::ObjectTree objectTree; boost::system::error_code ec = ipmi::getAllDbusObjects( ctx, systemRoot, sessionIntf, match, objectTree); if (ec) { phosphor::logging::log( "Failed to fetch PFR object from dbus", phosphor::logging::entry("INTERFACE=%s", sessionIntf), phosphor::logging::entry("ERROR=%s", ec.message().c_str())); return; } for (auto& softObject : objectTree) { const std::string& objPath = softObject.first; const std::string& serviceName = softObject.second.begin()->first; // PFR object found.. check for PFR support ipmi::PropertyMap result; ec = ipmi::getAllDbusProperties(ctx, serviceName, objPath, sessionIntf, result); if (ec) { phosphor::logging::log( "Failed to fetch pfr properties", phosphor::logging::entry("ERROR=%s", ec.message().c_str())); return; } const uint64_t* i2cBusNum = nullptr; const uint64_t* address = nullptr; for (const auto& [propName, propVariant] : result) { if (propName == "Address") { address = std::get_if(&propVariant); } else if (propName == "Bus") { i2cBusNum = std::get_if(&propVariant); } } if ((address == nullptr) || (i2cBusNum == nullptr)) { phosphor::logging::log( "Unable to read the pfr properties"); return; } bus = static_cast(*i2cBusNum); i2cBus = "/dev/i2c-" + std::to_string(bus); targetAddr = static_cast(*address); i2cConfigLoaded = true; } } void writefifo(const uint8_t cmdReg, const uint8_t val) { // Based on the spec, writing cmdReg to address val on this device, will // trigger the write FIFO operation. std::vector writeData = {cmdReg, val}; std::vector readBuf(0); ipmi::Cc retI2C = ipmi::i2cWriteRead(i2cBus, targetAddr, writeData, readBuf); if (retI2C) { phosphor::logging::log( "i2cWriteRead returns non-zero"); } } } // namespace mailbox ipmi::RspType ipmiOEMGetBmcVersionString() { static std::string version{}; if (version.empty()) { std::regex expr{"^VERSION_ID=(.*)$"}; static constexpr auto osReleasePath{"/etc/os-release"}; std::ifstream ifs(osReleasePath); if (!ifs.is_open()) { version = "os-release not present"; } std::string line{}; while (std::getline(ifs, line)) { std::smatch sm; if (regex_match(line, sm, expr)) { if (sm.size() == 2) { std::string v = sm[1].str(); // remove the quotes v.erase(std::remove(v.begin(), v.end(), '\"'), v.end()); version = v; break; } } } ifs.close(); if (version.empty()) { version = "VERSION_ID not present"; } } return ipmi::responseSuccess(version); } // Returns the Chassis Identifier (serial #) ipmi_ret_t ipmiOEMGetChassisIdentifier(ipmi_netfn_t, ipmi_cmd_t, ipmi_request_t, ipmi_response_t response, ipmi_data_len_t dataLen, ipmi_context_t) { std::string serial; if (*dataLen != 0) // invalid request if there are extra parameters { *dataLen = 0; return IPMI_CC_REQ_DATA_LEN_INVALID; } std::shared_ptr dbus = getSdBus(); if (getChassisSerialNumber(*dbus, serial) == 0) { *dataLen = serial.size(); // length will never exceed response length // as it is checked in getChassisSerialNumber char* resp = static_cast(response); serial.copy(resp, *dataLen); return IPMI_CC_OK; } *dataLen = 0; return IPMI_CC_RESPONSE_ERROR; } ipmi_ret_t ipmiOEMSetSystemGUID(ipmi_netfn_t, ipmi_cmd_t, ipmi_request_t request, ipmi_response_t, ipmi_data_len_t dataLen, ipmi_context_t) { static constexpr size_t safeBufferLength = 50; char buf[safeBufferLength] = {0}; GUIDData* Data = reinterpret_cast(request); if (*dataLen != sizeof(GUIDData)) // 16bytes { *dataLen = 0; return IPMI_CC_REQ_DATA_LEN_INVALID; } *dataLen = 0; snprintf( buf, safeBufferLength, "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x", Data->timeLow4, Data->timeLow3, Data->timeLow2, Data->timeLow1, Data->timeMid2, Data->timeMid1, Data->timeHigh2, Data->timeHigh1, Data->clock2, Data->clock1, Data->node6, Data->node5, Data->node4, Data->node3, Data->node2, Data->node1); // UUID is in RFC4122 format. Ex: 61a39523-78f2-11e5-9862-e6402cfc3223 std::string guid = buf; std::string objpath = "/xyz/openbmc_project/control/host0/systemGUID"; std::string intf = "xyz.openbmc_project.Common.UUID"; std::shared_ptr dbus = getSdBus(); std::string service = getService(*dbus, intf, objpath); setDbusProperty(*dbus, service, objpath, intf, "UUID", guid); return IPMI_CC_OK; } ipmi::RspType<> ipmiOEMDisableBMCSystemReset(bool disableResetOnSMI, uint7_t reserved1) { if (reserved1) { return ipmi::responseInvalidFieldRequest(); } std::shared_ptr busp = getSdBus(); try { auto service = ipmi::getService(*busp, bmcResetDisablesIntf, bmcResetDisablesPath); ipmi::setDbusProperty(*busp, service, bmcResetDisablesPath, bmcResetDisablesIntf, "ResetOnSMI", !disableResetOnSMI); } catch (const std::exception& e) { phosphor::logging::log( "Failed to set BMC reset disables", phosphor::logging::entry("EXCEPTION=%s", e.what())); return ipmi::responseUnspecifiedError(); } return ipmi::responseSuccess(); } ipmi::RspType ipmiOEMGetBMCResetDisables() { bool disableResetOnSMI = true; std::shared_ptr busp = getSdBus(); try { auto service = ipmi::getService(*busp, bmcResetDisablesIntf, bmcResetDisablesPath); Value variant = ipmi::getDbusProperty(*busp, service, bmcResetDisablesPath, bmcResetDisablesIntf, "ResetOnSMI"); disableResetOnSMI = !std::get(variant); } catch (const std::exception& e) { phosphor::logging::log( "Failed to get BMC reset disables", phosphor::logging::entry("EXCEPTION=%s", e.what())); return ipmi::responseUnspecifiedError(); } return ipmi::responseSuccess(disableResetOnSMI, 0); } ipmi_ret_t ipmiOEMSetBIOSID(ipmi_netfn_t, ipmi_cmd_t, ipmi_request_t request, ipmi_response_t response, ipmi_data_len_t dataLen, ipmi_context_t) { DeviceInfo* data = reinterpret_cast(request); if ((*dataLen < 2ul) || (*dataLen != (1ul + data->biosIDLength))) { *dataLen = 0; return IPMI_CC_REQ_DATA_LEN_INVALID; } std::string idString((char*)data->biosId, data->biosIDLength); for (auto idChar : idString) { if (!std::isprint(static_cast(idChar))) { phosphor::logging::log( "BIOS ID contains non printable character"); return IPMI_CC_INVALID_FIELD_REQUEST; } } std::shared_ptr dbus = getSdBus(); std::string service = getService(*dbus, biosVersionIntf, biosActiveObjPath); setDbusProperty(*dbus, service, biosActiveObjPath, biosVersionIntf, biosVersionProp, idString); uint8_t* bytesWritten = static_cast(response); *bytesWritten = data->biosIDLength; // how many bytes are written into storage *dataLen = 1; return IPMI_CC_OK; } bool getActiveHSCSoftwareVersionInfo(std::string& hscVersion, size_t hscNumber) { std::shared_ptr dbus = getSdBus(); try { std::string hsbpObjPath = "/xyz/openbmc_project/software/HSBP_" + std::to_string(hscNumber); auto service = getService(*dbus, biosVersionIntf, hsbpObjPath); Value hscVersionValue = getDbusProperty(*dbus, "xyz.openbmc_project.HsbpManager", hsbpObjPath, biosVersionIntf, "Version"); hscVersion = std::get(hscVersionValue); } catch (const sdbusplus::exception_t& e) { phosphor::logging::log( "Failed to retrieve HSBP version information", phosphor::logging::entry("HSBP Number=%d", hscNumber)); return false; } return true; } bool getHscVerInfo(ipmi::Context::ptr&, uint8_t& hsc0Major, uint8_t& hsc0Minor, uint8_t& hsc1Major, uint8_t& hsc1Minor, uint8_t& hsc2Major, uint8_t& hsc2Minor) { std::string hscVersion; std::array hscVersions{0}; for (size_t hscNumber = 1; hscNumber <= 3; hscNumber++) { if (!getActiveHSCSoftwareVersionInfo(hscVersion, hscNumber)) { continue; } std::regex pattern1("(\\d+?).(\\d+?).(\\d+?)"); constexpr size_t matchedPhosphor = 4; std::smatch results; // hscVersion = BOOT_VER.FPGA_VER.SECURITY_REVISION (Example: 00.02.01) if (std::regex_match(hscVersion, results, pattern1)) { // Major version is FPGA_VER and Minor version is SECURITY_REV if (results.size() == matchedPhosphor) { int index = (hscNumber - 1) * 2; hscVersions[index] = static_cast(std::stoi(results[2])); hscVersions[index + 1] = static_cast(std::stoi(results[3])); } } } hsc0Major = hscVersions[0]; hsc0Minor = hscVersions[1]; hsc1Major = hscVersions[2]; hsc1Minor = hscVersions[3]; hsc2Major = hscVersions[4]; hsc2Minor = hscVersions[5]; return true; } bool getSwVerInfo(ipmi::Context::ptr& ctx, uint8_t& bmcMajor, uint8_t& bmcMinor, uint8_t& meMajor, uint8_t& meMinor) { // step 1 : get BMC Major and Minor numbers from its DBUS property std::string bmcVersion; if (getActiveSoftwareVersionInfo(ctx, versionPurposeBMC, bmcVersion)) { return false; } std::optional rev = convertIntelVersion(bmcVersion); if (rev.has_value()) { MetaRevision revision = rev.value(); bmcMajor = revision.major; revision.minor = (revision.minor > 99 ? 99 : revision.minor); bmcMinor = revision.minor % 10 + (revision.minor / 10) * 16; } // step 2 : get ME Major and Minor numbers from its DBUS property std::string meVersion; if (getActiveSoftwareVersionInfo(ctx, versionPurposeME, meVersion)) { return false; } std::regex pattern1("(\\d+?).(\\d+?).(\\d+?).(\\d+?).(\\d+?)"); constexpr size_t matchedPhosphor = 6; std::smatch results; if (std::regex_match(meVersion, results, pattern1)) { if (results.size() == matchedPhosphor) { meMajor = static_cast(std::stoi(results[1])); meMinor = static_cast( std::stoi(results[2]) << 4 | std::stoi(results[3])); } } return true; } ipmi::RspType< std::variant, std::array, std::array, std::array, std::array>, std::tuple>>> ipmiOEMGetDeviceInfo(ipmi::Context::ptr& ctx, uint8_t entityType, std::optional countToRead, std::optional offset) { if (entityType > static_cast(OEMDevEntityType::sdrVer)) { return ipmi::responseInvalidFieldRequest(); } // handle OEM command items switch (OEMDevEntityType(entityType)) { case OEMDevEntityType::biosId: { // Byte 2&3, Only used with selecting BIOS if (!countToRead || !offset) { return ipmi::responseReqDataLenInvalid(); } std::shared_ptr dbus = getSdBus(); std::string service = getService(*dbus, biosVersionIntf, biosActiveObjPath); try { Value variant = getDbusProperty(*dbus, service, biosActiveObjPath, biosVersionIntf, biosVersionProp); std::string& idString = std::get(variant); if (*offset >= idString.size()) { return ipmi::responseParmOutOfRange(); } size_t length = 0; if (*countToRead > (idString.size() - *offset)) { length = idString.size() - *offset; } else { length = *countToRead; } std::string readBuf = {0}; readBuf.resize(length); std::copy_n(idString.begin() + *offset, length, (readBuf.begin())); return ipmi::responseSuccess(readBuf); } catch (const std::bad_variant_access& e) { return ipmi::responseUnspecifiedError(); } } break; case OEMDevEntityType::devVer: { // Byte 2&3, Only used with selecting BIOS if (countToRead || offset) { return ipmi::responseReqDataLenInvalid(); } constexpr const size_t verLen = 2; constexpr const size_t verTotalLen = 10; std::array bmcBuf = {0xff, 0xff}; std::array hsc0Buf = {0xff, 0xff}; std::array hsc1Buf = {0xff, 0xff}; std::array meBuf = {0xff, 0xff}; std::array hsc2Buf = {0xff, 0xff}; // data0/1: BMC version number; data6/7: ME version number if (!getSwVerInfo(ctx, bmcBuf[0], bmcBuf[1], meBuf[0], meBuf[1])) { return ipmi::responseUnspecifiedError(); } if (!getHscVerInfo(ctx, hsc0Buf[0], hsc0Buf[1], hsc1Buf[0], hsc1Buf[1], hsc2Buf[0], hsc2Buf[1])) { return ipmi::responseUnspecifiedError(); } return ipmi::responseSuccess( std::tuple< uint8_t, std::array, std::array, std::array, std::array, std::array>{ verTotalLen, bmcBuf, hsc0Buf, hsc1Buf, meBuf, hsc2Buf}); } break; case OEMDevEntityType::sdrVer: { // Byte 2&3, Only used with selecting BIOS if (countToRead || offset) { return ipmi::responseReqDataLenInvalid(); } constexpr const size_t sdrLen = 2; std::array readBuf = {0x01, 0x0}; return ipmi::responseSuccess( std::tuple>{sdrLen, readBuf}); } break; default: return ipmi::responseInvalidFieldRequest(); } } ipmi_ret_t ipmiOEMGetAICFRU(ipmi_netfn_t, ipmi_cmd_t, ipmi_request_t, ipmi_response_t response, ipmi_data_len_t dataLen, ipmi_context_t) { if (*dataLen != 0) { *dataLen = 0; return IPMI_CC_REQ_DATA_LEN_INVALID; } *dataLen = 1; uint8_t* res = reinterpret_cast(response); // temporary fix. We don't support AIC FRU now. Just tell BIOS that no // AIC is available so that BIOS will not timeout repeatly which leads to // slow booting. *res = 0; // Byte1=Count of SlotPosition/FruID records. return IPMI_CC_OK; } ipmi_ret_t ipmiOEMGetPowerRestoreDelay(ipmi_netfn_t, ipmi_cmd_t, ipmi_request_t, ipmi_response_t response, ipmi_data_len_t dataLen, ipmi_context_t) { GetPowerRestoreDelayRes* resp = reinterpret_cast(response); if (*dataLen != 0) { *dataLen = 0; return IPMI_CC_REQ_DATA_LEN_INVALID; } std::shared_ptr dbus = getSdBus(); std::string service = getService(*dbus, powerRestoreDelayIntf, powerRestoreDelayObjPath); Value variant = getDbusProperty(*dbus, service, powerRestoreDelayObjPath, powerRestoreDelayIntf, powerRestoreDelayProp); uint64_t val = std::get(variant); val /= 1000000UL; uint16_t delay = val; resp->byteLSB = delay; resp->byteMSB = delay >> 8; *dataLen = sizeof(GetPowerRestoreDelayRes); return IPMI_CC_OK; } static uint8_t bcdToDec(uint8_t val) { return ((val / 16 * 10) + (val % 16)); } // Allows an update utility or system BIOS to send the status of an embedded // firmware update attempt to the BMC. After received, BMC will create a logging // record. ipmi::RspType<> ipmiOEMSendEmbeddedFwUpdStatus( uint8_t status, uint8_t target, uint8_t majorRevision, uint8_t minorRevision, uint32_t auxInfo) { std::string firmware; int instance = (target & targetInstanceMask) >> targetInstanceShift; target = (target & selEvtTargetMask) >> selEvtTargetShift; /* make sure the status is 0, 1, or 2 as per the spec */ if (status > 2) { return ipmi::response(ipmi::ccInvalidFieldRequest); } /* make sure the target is 0, 1, 2, or 4 as per the spec */ if (target > 4 || target == 3) { return ipmi::response(ipmi::ccInvalidFieldRequest); } /*orignal OEM command is to record OEM SEL. But openbmc does not support OEM SEL, so we redirect it to redfish event logging. */ std::string buildInfo; std::string action; switch (FWUpdateTarget(target)) { case FWUpdateTarget::targetBMC: firmware = "BMC"; buildInfo = "major: " + std::to_string(majorRevision) + " minor: " + std::to_string(bcdToDec(minorRevision)) + // BCD encoded " BuildID: " + std::to_string(auxInfo); buildInfo += std::to_string(auxInfo); break; case FWUpdateTarget::targetBIOS: firmware = "BIOS"; buildInfo = "major: " + std::to_string(bcdToDec(majorRevision)) + // BCD encoded " minor: " + std::to_string(bcdToDec(minorRevision)) + // BCD encoded " ReleaseNumber: " + // ASCII encoded std::to_string(static_cast(auxInfo >> 0) - '0') + std::to_string(static_cast(auxInfo >> 8) - '0') + std::to_string(static_cast(auxInfo >> 16) - '0') + std::to_string(static_cast(auxInfo >> 24) - '0'); break; case FWUpdateTarget::targetME: firmware = "ME"; buildInfo = "major: " + std::to_string(majorRevision) + " minor1: " + std::to_string(bcdToDec(minorRevision)) + // BCD encoded " minor2: " + std::to_string(bcdToDec(static_cast(auxInfo >> 0))) + " build1: " + std::to_string(bcdToDec(static_cast(auxInfo >> 8))) + " build2: " + std::to_string(bcdToDec(static_cast(auxInfo >> 16))); break; case FWUpdateTarget::targetOEMEWS: firmware = "EWS"; buildInfo = "major: " + std::to_string(majorRevision) + " minor: " + std::to_string(bcdToDec(minorRevision)) + // BCD encoded " BuildID: " + std::to_string(auxInfo); break; } static const std::string openBMCMessageRegistryVersion("0.1"); std::string redfishMsgID = "OpenBMC." + openBMCMessageRegistryVersion; switch (status) { case 0x0: action = "update started"; redfishMsgID += ".FirmwareUpdateStarted"; break; case 0x1: action = "update completed successfully"; redfishMsgID += ".FirmwareUpdateCompleted"; break; case 0x2: action = "update failure"; redfishMsgID += ".FirmwareUpdateFailed"; break; default: action = "unknown"; break; } std::string firmwareInstanceStr = firmware + " instance: " + std::to_string(instance); std::string message("[firmware update] " + firmwareInstanceStr + " status: <" + action + "> " + buildInfo); sd_journal_send("MESSAGE=%s", message.c_str(), "PRIORITY=%i", LOG_INFO, "REDFISH_MESSAGE_ID=%s", redfishMsgID.c_str(), "REDFISH_MESSAGE_ARGS=%s,%s", firmwareInstanceStr.c_str(), buildInfo.c_str(), NULL); return ipmi::responseSuccess(); } ipmi::RspType> ipmiOEMSlotIpmb( ipmi::Context::ptr& ctx, uint6_t reserved1, uint2_t slotNumber, uint3_t baseBoardSlotNum, [[maybe_unused]] uint3_t riserSlotNum, uint2_t reserved2, uint8_t targetAddr, uint8_t netFn, uint8_t cmd, std::optional> writeData) { if (reserved1 || reserved2) { return ipmi::responseInvalidFieldRequest(); } boost::system::error_code ec; using ipmbResponse = std::tuple>; ipmbResponse res = ctx->bus->yield_method_call( ctx->yield, ec, "xyz.openbmc_project.Ipmi.Channel.Ipmb", "/xyz/openbmc_project/Ipmi/Channel/Ipmb", "org.openbmc.Ipmb", "SlotIpmbRequest", static_cast(slotNumber), static_cast(baseBoardSlotNum), targetAddr, netFn, cmd, *writeData); if (ec) { phosphor::logging::log( "Failed to call dbus method SlotIpmbRequest"); return ipmi::responseUnspecifiedError(); } std::vector dataReceived(0); int status = -1; uint8_t resNetFn = 0, resLun = 0, resCmd = 0, cc = 0; std::tie(status, resNetFn, resLun, resCmd, cc, dataReceived) = res; if (status) { phosphor::logging::log( "Failed to get response from SlotIpmbRequest"); return ipmi::responseResponseError(); } return ipmi::responseSuccess(cc, dataReceived); } ipmi_ret_t ipmiOEMSetPowerRestoreDelay(ipmi_netfn_t, ipmi_cmd_t, ipmi_request_t request, ipmi_response_t, ipmi_data_len_t dataLen, ipmi_context_t) { SetPowerRestoreDelayReq* data = reinterpret_cast(request); uint16_t delay = 0; if (*dataLen != sizeof(SetPowerRestoreDelayReq)) { *dataLen = 0; return IPMI_CC_REQ_DATA_LEN_INVALID; } delay = data->byteMSB; delay = (delay << 8) | data->byteLSB; uint64_t val = delay * 1000000; std::shared_ptr dbus = getSdBus(); std::string service = getService(*dbus, powerRestoreDelayIntf, powerRestoreDelayObjPath); setDbusProperty(*dbus, service, powerRestoreDelayObjPath, powerRestoreDelayIntf, powerRestoreDelayProp, val); *dataLen = 0; return IPMI_CC_OK; } static bool cpuPresent(const std::string& cpuName) { static constexpr const char* cpuPresencePathPrefix = "/xyz/openbmc_project/inventory/system/chassis/motherboard/"; static constexpr const char* cpuPresenceIntf = "xyz.openbmc_project.Inventory.Item"; std::string cpuPresencePath = cpuPresencePathPrefix + cpuName; std::shared_ptr busp = getSdBus(); try { auto service = ipmi::getService(*busp, cpuPresenceIntf, cpuPresencePath); ipmi::Value result = ipmi::getDbusProperty( *busp, service, cpuPresencePath, cpuPresenceIntf, "Present"); return std::get(result); } catch (const std::exception& e) { phosphor::logging::log( "Cannot find processor presence", phosphor::logging::entry("NAME=%s", cpuName.c_str())); return false; } } ipmi::RspType ipmiOEMGetProcessorErrConfig() { bool resetOnIERR = false; bool resetOnERR2 = false; bool resetOnMCERR = false; uint6_t cpu1IERRCount = 0; uint6_t cpu2IERRCount = 0; uint6_t cpu3IERRCount = 0; uint6_t cpu4IERRCount = 0; uint8_t crashdumpCount = 0; uint2_t cpu1Status = cpuPresent("CPU_1") ? types::enum_cast(CPUStatus::enabled) : types::enum_cast(CPUStatus::notPresent); uint2_t cpu2Status = cpuPresent("CPU_2") ? types::enum_cast(CPUStatus::enabled) : types::enum_cast(CPUStatus::notPresent); uint2_t cpu3Status = cpuPresent("CPU_3") ? types::enum_cast(CPUStatus::enabled) : types::enum_cast(CPUStatus::notPresent); uint2_t cpu4Status = cpuPresent("CPU_4") ? types::enum_cast(CPUStatus::enabled) : types::enum_cast(CPUStatus::notPresent); std::shared_ptr busp = getSdBus(); try { auto service = ipmi::getService(*busp, processorErrConfigIntf, processorErrConfigObjPath); ipmi::PropertyMap result = ipmi::getAllDbusProperties( *busp, service, processorErrConfigObjPath, processorErrConfigIntf); resetOnIERR = std::get(result.at("ResetOnIERR")); resetOnERR2 = std::get(result.at("ResetOnERR2")); resetOnMCERR = std::get(result.at("ResetOnMCERR")); cpu1IERRCount = std::get(result.at("ErrorCountCPU1")); cpu2IERRCount = std::get(result.at("ErrorCountCPU2")); cpu3IERRCount = std::get(result.at("ErrorCountCPU3")); cpu4IERRCount = std::get(result.at("ErrorCountCPU4")); crashdumpCount = std::get(result.at("CrashdumpCount")); } catch (const std::exception& e) { phosphor::logging::log( "Failed to fetch processor error config", phosphor::logging::entry("ERROR=%s", e.what())); return ipmi::responseUnspecifiedError(); } return ipmi::responseSuccess( resetOnIERR, resetOnERR2, resetOnMCERR, 0, 0x3F, cpu1IERRCount, cpu1Status, cpu2IERRCount, cpu2Status, cpu3IERRCount, cpu3Status, cpu4IERRCount, cpu4Status, crashdumpCount); } ipmi::RspType<> ipmiOEMSetProcessorErrConfig( bool resetOnIERR, bool resetOnERR2, bool resetOnMCERR, uint5_t reserved1, uint8_t reserved2, std::optional clearCPUErrorCount, std::optional clearCrashdumpCount, std::optional reserved3) { if (reserved1 || reserved2) { return ipmi::responseInvalidFieldRequest(); } std::shared_ptr busp = getSdBus(); try { if (reserved3.value_or(0)) { return ipmi::responseInvalidFieldRequest(); } auto service = ipmi::getService(*busp, processorErrConfigIntf, processorErrConfigObjPath); ipmi::setDbusProperty(*busp, service, processorErrConfigObjPath, processorErrConfigIntf, "ResetOnIERR", resetOnIERR); ipmi::setDbusProperty(*busp, service, processorErrConfigObjPath, processorErrConfigIntf, "ResetOnERR2", resetOnERR2); ipmi::setDbusProperty(*busp, service, processorErrConfigObjPath, processorErrConfigIntf, "ResetOnMCERR", resetOnMCERR); if (clearCPUErrorCount.value_or(false)) { ipmi::setDbusProperty(*busp, service, processorErrConfigObjPath, processorErrConfigIntf, "ErrorCountCPU1", static_cast(0)); ipmi::setDbusProperty(*busp, service, processorErrConfigObjPath, processorErrConfigIntf, "ErrorCountCPU2", static_cast(0)); ipmi::setDbusProperty(*busp, service, processorErrConfigObjPath, processorErrConfigIntf, "ErrorCountCPU3", static_cast(0)); ipmi::setDbusProperty(*busp, service, processorErrConfigObjPath, processorErrConfigIntf, "ErrorCountCPU4", static_cast(0)); } if (clearCrashdumpCount.value_or(false)) { ipmi::setDbusProperty(*busp, service, processorErrConfigObjPath, processorErrConfigIntf, "CrashdumpCount", static_cast(0)); } } catch (const std::exception& e) { phosphor::logging::log( "Failed to set processor error config", phosphor::logging::entry("EXCEPTION=%s", e.what())); return ipmi::responseUnspecifiedError(); } return ipmi::responseSuccess(); } ipmi_ret_t ipmiOEMGetShutdownPolicy(ipmi_netfn_t, ipmi_cmd_t, ipmi_request_t, ipmi_response_t response, ipmi_data_len_t dataLen, ipmi_context_t) { GetOEMShutdownPolicyRes* resp = reinterpret_cast(response); if (*dataLen != 0) { phosphor::logging::log( "oem_get_shutdown_policy: invalid input len!"); *dataLen = 0; return IPMI_CC_REQ_DATA_LEN_INVALID; } *dataLen = 0; try { std::shared_ptr dbus = getSdBus(); std::string service = getService(*dbus, oemShutdownPolicyIntf, oemShutdownPolicyObjPath); Value variant = getDbusProperty( *dbus, service, oemShutdownPolicyObjPath, oemShutdownPolicyIntf, oemShutdownPolicyObjPathProp); if (sdbusplus::com::intel::Control::server::OCOTShutdownPolicy:: convertPolicyFromString(std::get(variant)) == sdbusplus::com::intel::Control::server::OCOTShutdownPolicy::Policy:: NoShutdownOnOCOT) { resp->policy = 0; } else if (sdbusplus::com::intel::Control::server::OCOTShutdownPolicy:: convertPolicyFromString(std::get(variant)) == sdbusplus::com::intel::Control::server::OCOTShutdownPolicy:: Policy::ShutdownOnOCOT) { resp->policy = 1; } else { phosphor::logging::log( "oem_set_shutdown_policy: invalid property!", phosphor::logging::entry( "PROP=%s", std::get(variant).c_str())); return IPMI_CC_UNSPECIFIED_ERROR; } // TODO needs to check if it is multi-node products, // policy is only supported on node 3/4 resp->policySupport = shutdownPolicySupported; } catch (const sdbusplus::exception_t& e) { phosphor::logging::log(e.description()); return IPMI_CC_UNSPECIFIED_ERROR; } *dataLen = sizeof(GetOEMShutdownPolicyRes); return IPMI_CC_OK; } ipmi_ret_t ipmiOEMSetShutdownPolicy(ipmi_netfn_t, ipmi_cmd_t, ipmi_request_t request, ipmi_response_t, ipmi_data_len_t dataLen, ipmi_context_t) { uint8_t* req = reinterpret_cast(request); sdbusplus::com::intel::Control::server::OCOTShutdownPolicy::Policy policy = sdbusplus::com::intel::Control::server::OCOTShutdownPolicy::Policy:: NoShutdownOnOCOT; // TODO needs to check if it is multi-node products, // policy is only supported on node 3/4 if (*dataLen != 1) { phosphor::logging::log( "oem_set_shutdown_policy: invalid input len!"); *dataLen = 0; return IPMI_CC_REQ_DATA_LEN_INVALID; } *dataLen = 0; if ((*req != noShutdownOnOCOT) && (*req != shutdownOnOCOT)) { phosphor::logging::log( "oem_set_shutdown_policy: invalid input!"); return IPMI_CC_INVALID_FIELD_REQUEST; } if (*req == noShutdownOnOCOT) { policy = sdbusplus::com::intel::Control::server::OCOTShutdownPolicy:: Policy::NoShutdownOnOCOT; } else { policy = sdbusplus::com::intel::Control::server::OCOTShutdownPolicy:: Policy::ShutdownOnOCOT; } try { std::shared_ptr dbus = getSdBus(); std::string service = getService(*dbus, oemShutdownPolicyIntf, oemShutdownPolicyObjPath); setDbusProperty( *dbus, service, oemShutdownPolicyObjPath, oemShutdownPolicyIntf, oemShutdownPolicyObjPathProp, sdbusplus::com::intel::Control::server::convertForMessage(policy)); } catch (const sdbusplus::exception_t& e) { phosphor::logging::log(e.description()); return IPMI_CC_UNSPECIFIED_ERROR; } return IPMI_CC_OK; } /** @brief implementation for check the DHCP or not in IPv4 * @param[in] Channel - Channel number * @returns true or false. */ static bool isDHCPEnabled(uint8_t Channel) { try { auto ethdevice = getChannelName(Channel); if (ethdevice.empty()) { return false; } auto ethIP = ethdevice + "/ipv4"; std::shared_ptr dbus = getSdBus(); auto ethernetObj = getDbusObject(*dbus, networkIPIntf, networkRoot, ethIP); auto value = getDbusProperty(*dbus, networkService, ethernetObj.first, networkIPIntf, "Origin"); if (std::get(value) == "xyz.openbmc_project.Network.IP.AddressOrigin.DHCP") { return true; } else { return false; } } catch (const sdbusplus::exception_t& e) { phosphor::logging::log(e.description()); return true; } } /** @brief implementes for check the DHCP or not in IPv6 * @param[in] Channel - Channel number * @returns true or false. */ static bool isDHCPIPv6Enabled(uint8_t Channel) { try { auto ethdevice = getChannelName(Channel); if (ethdevice.empty()) { return false; } auto ethIP = ethdevice + "/ipv6"; std::shared_ptr dbus = getSdBus(); auto objectInfo = getDbusObject(*dbus, networkIPIntf, networkRoot, ethIP); auto properties = getAllDbusProperties(*dbus, objectInfo.second, objectInfo.first, networkIPIntf); if (std::get(properties["Origin"]) == "xyz.openbmc_project.Network.IP.AddressOrigin.DHCP") { return true; } else { return false; } } catch (const sdbusplus::exception_t& e) { phosphor::logging::log(e.description()); return true; } } /** @brief implementes the creating of default new user * @param[in] userName - new username in 16 bytes. * @param[in] userPassword - new password in 20 bytes * @returns ipmi completion code. */ ipmi::RspType<> ipmiOEMSetUser2Activation( std::array& userName, const SecureBuffer& userPassword) { if (userPassword.size() != ipmi::maxIpmi20PasswordSize) { return ipmi::responseReqDataLenInvalid(); } bool userState = false; // Check for System Interface not exist and LAN should be static for (uint8_t channel = 0; channel < maxIpmiChannels; channel++) { ChannelInfo chInfo{}; try { getChannelInfo(channel, chInfo); } catch (const sdbusplus::exception_t& e) { phosphor::logging::log( "ipmiOEMSetUser2Activation: Failed to get Channel Info", phosphor::logging::entry("MSG: %s", e.description())); return ipmi::response(ipmi::ccUnspecifiedError); } if (chInfo.mediumType == static_cast(EChannelMediumType::systemInterface)) { phosphor::logging::log( "ipmiOEMSetUser2Activation: system interface exist ."); return ipmi::response(ipmi::ccCommandNotAvailable); } else { if (chInfo.mediumType == static_cast(EChannelMediumType::lan8032)) { if (isDHCPIPv6Enabled(channel) || isDHCPEnabled(channel)) { phosphor::logging::log( "ipmiOEMSetUser2Activation: DHCP enabled ."); return ipmi::response(ipmi::ccCommandNotAvailable); } } } } uint8_t maxChUsers = 0, enabledUsers = 0, fixedUsers = 0; if (ipmi::ccSuccess == ipmiUserGetAllCounts(maxChUsers, enabledUsers, fixedUsers)) { if (enabledUsers > 1) { phosphor::logging::log( "ipmiOEMSetUser2Activation: more than one user is enabled."); return ipmi::response(ipmi::ccCommandNotAvailable); } // Check the user 2 is enabled or not ipmiUserCheckEnabled(ipmiDefaultUserId, userState); if (userState == true) { phosphor::logging::log( "ipmiOEMSetUser2Activation: user 2 already enabled ."); return ipmi::response(ipmi::ccCommandNotAvailable); } } else { return ipmi::response(ipmi::ccUnspecifiedError); } #if BYTE_ORDER == LITTLE_ENDIAN PrivAccess privAccess = {PRIVILEGE_ADMIN, true, true, true, 0}; #endif #if BYTE_ORDER == BIG_ENDIAN PrivAccess privAccess = {0, true, true, true, PRIVILEGE_ADMIN}; #endif // ipmiUserSetUserName correctly handles char*, possibly non-null // terminated strings using ipmiMaxUserName size size_t nameLen = strnlen(reinterpret_cast(userName.data()), sizeof(userName)); const std::string userNameRaw( reinterpret_cast(userName.data()), nameLen); if (ipmi::ccSuccess == ipmiUserSetUserName(ipmiDefaultUserId, userNameRaw)) { if (ipmi::ccSuccess == ipmiUserSetUserPassword( ipmiDefaultUserId, reinterpret_cast(userPassword.data()))) { if (ipmi::ccSuccess == ipmiUserSetPrivilegeAccess( ipmiDefaultUserId, static_cast(ipmi::EChannelID::chanLan1), privAccess, true)) { phosphor::logging::log( "ipmiOEMSetUser2Activation: user created successfully "); return ipmi::responseSuccess(); } } // we need to delete the default user id which added in this command as // password / priv setting is failed. ipmiUserSetUserName(ipmiDefaultUserId, static_cast("")); phosphor::logging::log( "ipmiOEMSetUser2Activation: password / priv setting is failed."); } else { phosphor::logging::log( "ipmiOEMSetUser2Activation: Setting username failed."); } return ipmi::response(ipmi::ccCommandNotAvailable); } /** @brief implementes executing the linux command * @param[in] linux command * @returns status */ static uint8_t executeCmd(const char* path) { boost::process::child execProg(path); execProg.wait(); int retCode = execProg.exit_code(); if (retCode) { return ipmi::ccUnspecifiedError; } return ipmi::ccSuccess; } /** @brief implementes ASD Security event logging * @param[in] Event message string * @param[in] Event Severity * @returns status */ static void atScaleDebugEventlog(std::string msg, int severity) { std::string eventStr = "OpenBMC.0.1." + msg; sd_journal_send("MESSAGE=Security Event: %s", eventStr.c_str(), "PRIORITY=%i", severity, "REDFISH_MESSAGE_ID=%s", eventStr.c_str(), NULL); } /** @brief implementes setting password for special user * @param[in] specialUserIndex * @param[in] userPassword - new password in 20 bytes * @returns ipmi completion code. */ ipmi::RspType<> ipmiOEMSetSpecialUserPassword(ipmi::Context::ptr& ctx, uint8_t specialUserIndex, std::vector userPassword) { ChannelInfo chInfo; ipmi_ret_t status = ipmi::ccSuccess; try { getChannelInfo(ctx->channel, chInfo); } catch (const sdbusplus::exception_t& e) { phosphor::logging::log( "ipmiOEMSetSpecialUserPassword: Failed to get Channel Info", phosphor::logging::entry("MSG: %s", e.description())); return ipmi::responseUnspecifiedError(); } if (chInfo.mediumType != static_cast(EChannelMediumType::systemInterface)) { phosphor::logging::log( "ipmiOEMSetSpecialUserPassword: Error - supported only in KCS " "interface"); return ipmi::responseCommandNotAvailable(); } // 0 for root user and 1 for AtScaleDebug is allowed if (specialUserIndex > static_cast(SpecialUserIndex::atScaleDebugUser)) { phosphor::logging::log( "ipmiOEMSetSpecialUserPassword: Invalid user account"); return ipmi::responseParmOutOfRange(); } if (userPassword.size() != 0) { constexpr uint8_t minPasswordSizeRequired = 6; SecureString passwd; if (userPassword.size() < minPasswordSizeRequired || userPassword.size() > ipmi::maxIpmi20PasswordSize) { OPENSSL_cleanse(userPassword.data(), userPassword.size()); return ipmi::responseReqDataLenInvalid(); } passwd.assign(reinterpret_cast(userPassword.data()), userPassword.size()); // Clear sensitive data OPENSSL_cleanse(userPassword.data(), userPassword.size()); if (specialUserIndex == static_cast(SpecialUserIndex::atScaleDebugUser)) { status = ipmiSetSpecialUserPassword("asdbg", passwd); atScaleDebugEventlog("AtScaleDebugSpecialUserEnabled", LOG_CRIT); } else { status = ipmiSetSpecialUserPassword("root", passwd); } return ipmi::response(status); } else { if (specialUserIndex == static_cast(SpecialUserIndex::rootUser)) { status = executeCmd("passwd -d root"); } else { status = executeCmd("passwd -d asdbg"); if (status == 0) { atScaleDebugEventlog("AtScaleDebugSpecialUserDisabled", LOG_INFO); } } return ipmi::response(status); } } namespace ledAction { using namespace sdbusplus::xyz::openbmc_project::Led::server; std::map actionDbusToIpmi = { {Physical::Action::Off, 0}, {Physical::Action::On, 2}, {Physical::Action::Blink, 1}}; std::map offsetObjPath = { {2, statusAmberObjPath}, {4, statusGreenObjPath}, {6, identifyLEDObjPath}}; } // namespace ledAction int8_t getLEDState(sdbusplus::bus_t& bus, const std::string& intf, const std::string& objPath, uint8_t& state) { try { std::string service = getService(bus, intf, objPath); Value stateValue = getDbusProperty(bus, service, objPath, intf, "State"); std::string strState = std::get(stateValue); state = ledAction::actionDbusToIpmi.at( sdbusplus::xyz::openbmc_project::Led::server::Physical:: convertActionFromString(strState)); } catch (const sdbusplus::exception_t& e) { phosphor::logging::log(e.what()); return -1; } return 0; } ipmi::RspType ipmiOEMGetLEDStatus() { uint8_t ledstate = 0; phosphor::logging::log("GET led status"); std::shared_ptr dbus = getSdBus(); for (auto it = ledAction::offsetObjPath.begin(); it != ledAction::offsetObjPath.end(); ++it) { uint8_t state = 0; if (getLEDState(*dbus, ledIntf, it->second, state) == -1) { phosphor::logging::log( "oem_get_led_status: fail to get ID LED status!"); return ipmi::responseUnspecifiedError(); } ledstate |= state << it->first; } return ipmi::responseSuccess(ledstate); } ipmi_ret_t ipmiOEMCfgHostSerialPortSpeed( ipmi_netfn_t, ipmi_cmd_t, ipmi_request_t request, ipmi_response_t response, ipmi_data_len_t dataLen, ipmi_context_t) { CfgHostSerialReq* req = reinterpret_cast(request); uint8_t* resp = reinterpret_cast(response); if (*dataLen == 0) { phosphor::logging::log( "CfgHostSerial: invalid input len!", phosphor::logging::entry("LEN=%d", *dataLen)); return IPMI_CC_REQ_DATA_LEN_INVALID; } switch (req->command) { case getHostSerialCfgCmd: { if (*dataLen != 1) { phosphor::logging::log( "CfgHostSerial: invalid input len!"); *dataLen = 0; return IPMI_CC_REQ_DATA_LEN_INVALID; } *dataLen = 0; boost::process::ipstream is; std::vector data; std::string line; boost::process::child c1(fwGetEnvCmd, "-n", fwHostSerailCfgEnvName, boost::process::std_out > is); while (c1.running() && std::getline(is, line) && !line.empty()) { data.push_back(line); } c1.wait(); if (c1.exit_code()) { phosphor::logging::log( "CfgHostSerial:: error on execute", phosphor::logging::entry("EXECUTE=%s", fwSetEnvCmd)); // Using the default value *resp = 0; } else { if (data.size() != 1) { phosphor::logging::log( "CfgHostSerial:: error on read env"); return IPMI_CC_UNSPECIFIED_ERROR; } try { unsigned long tmp = std::stoul(data[0]); if (tmp > std::numeric_limits::max()) { throw std::out_of_range("Out of range"); } *resp = static_cast(tmp); } catch (const std::invalid_argument& e) { phosphor::logging::log( "invalid config ", phosphor::logging::entry("ERR=%s", e.what())); return IPMI_CC_UNSPECIFIED_ERROR; } catch (const std::out_of_range& e) { phosphor::logging::log( "out_of_range config ", phosphor::logging::entry("ERR=%s", e.what())); return IPMI_CC_UNSPECIFIED_ERROR; } } *dataLen = 1; break; } case setHostSerialCfgCmd: { if (*dataLen != sizeof(CfgHostSerialReq)) { phosphor::logging::log( "CfgHostSerial: invalid input len!"); *dataLen = 0; return IPMI_CC_REQ_DATA_LEN_INVALID; } *dataLen = 0; if (req->parameter > HostSerialCfgParamMax) { phosphor::logging::log( "CfgHostSerial: invalid input!"); return IPMI_CC_INVALID_FIELD_REQUEST; } boost::process::child c1(fwSetEnvCmd, fwHostSerailCfgEnvName, std::to_string(req->parameter)); c1.wait(); if (c1.exit_code()) { phosphor::logging::log( "CfgHostSerial:: error on execute", phosphor::logging::entry("EXECUTE=%s", fwGetEnvCmd)); return IPMI_CC_UNSPECIFIED_ERROR; } break; } default: phosphor::logging::log( "CfgHostSerial: invalid input!"); *dataLen = 0; return IPMI_CC_INVALID_FIELD_REQUEST; } return IPMI_CC_OK; } constexpr const char* thermalModeInterface = "xyz.openbmc_project.Control.ThermalMode"; constexpr const char* thermalModePath = "/xyz/openbmc_project/control/thermal_mode"; bool getFanProfileInterface( sdbusplus::bus_t& bus, boost::container::flat_map& resp) { auto call = bus.new_method_call(settingsBusName, thermalModePath, PROP_INTF, "GetAll"); call.append(thermalModeInterface); try { auto data = bus.call(call); data.read(resp); } catch (const sdbusplus::exception_t& e) { phosphor::logging::log( "getFanProfileInterface: can't get thermal mode!", phosphor::logging::entry("ERR=%s", e.what())); return false; } return true; } /**@brief implements the OEM set fan config. * @param selectedFanProfile - fan profile to enable * @param reserved1 * @param performanceMode - Performance/Acoustic mode * @param reserved2 * @param setPerformanceMode - set Performance/Acoustic mode * @param setFanProfile - set fan profile * * @return IPMI completion code. **/ ipmi::RspType<> ipmiOEMSetFanConfig( [[maybe_unused]] uint8_t selectedFanProfile, uint2_t reserved1, bool performanceMode, uint3_t reserved2, bool setPerformanceMode, [[maybe_unused]] bool setFanProfile, std::optional dimmGroupId, [[maybe_unused]] std::optional dimmPresenceBitmap) { if (reserved1 || reserved2) { return ipmi::responseInvalidFieldRequest(); } if (dimmGroupId) { if (*dimmGroupId >= maxCPUNum) { return ipmi::responseInvalidFieldRequest(); } if (!cpuPresent("cpu" + std::to_string(*dimmGroupId))) { return ipmi::responseInvalidFieldRequest(); } } // todo: tell bios to only send first 2 bytes boost::container::flat_map profileData; std::shared_ptr dbus = getSdBus(); if (!getFanProfileInterface(*dbus, profileData)) { return ipmi::responseUnspecifiedError(); } std::vector* supported = std::get_if>(&profileData["Supported"]); if (supported == nullptr) { return ipmi::responseInvalidFieldRequest(); } std::string mode; if (setPerformanceMode) { if (performanceMode) { if (std::find(supported->begin(), supported->end(), "Performance") != supported->end()) { mode = "Performance"; } } else { if (std::find(supported->begin(), supported->end(), "Acoustic") != supported->end()) { mode = "Acoustic"; } } if (mode.empty()) { return ipmi::responseInvalidFieldRequest(); } try { setDbusProperty(*dbus, settingsBusName, thermalModePath, thermalModeInterface, "Current", mode); } catch (const sdbusplus::exception_t& e) { phosphor::logging::log( "ipmiOEMSetFanConfig: can't set thermal mode!", phosphor::logging::entry("EXCEPTION=%s", e.what())); return ipmi::responseResponseError(); } } return ipmi::responseSuccess(); } ipmi::RspType ipmiOEMGetFanConfig(uint8_t dimmGroupId) { if (dimmGroupId >= maxCPUNum) { return ipmi::responseInvalidFieldRequest(); } bool cpuStatus = cpuPresent("cpu" + std::to_string(dimmGroupId)); if (!cpuStatus) { return ipmi::responseInvalidFieldRequest(); } boost::container::flat_map profileData; std::shared_ptr dbus = getSdBus(); if (!getFanProfileInterface(*dbus, profileData)) { return ipmi::responseResponseError(); } std::string* current = std::get_if(&profileData["Current"]); if (current == nullptr) { phosphor::logging::log( "ipmiOEMGetFanConfig: can't get current mode!"); return ipmi::responseResponseError(); } bool performance = (*current == "Performance"); uint8_t flags = 0; if (performance) { flags |= 1 << 2; } constexpr uint8_t fanControlDefaultProfile = 0x80; constexpr uint8_t fanControlProfileState = 0x00; constexpr uint32_t dimmPresenceBitmap = 0x00; return ipmi::responseSuccess(fanControlDefaultProfile, fanControlProfileState, flags, dimmPresenceBitmap); } constexpr const char* cfmLimitSettingPath = "/xyz/openbmc_project/control/cfm_limit"; constexpr const char* cfmLimitIface = "xyz.openbmc_project.Control.CFMLimit"; constexpr const size_t legacyExitAirSensorNumber = 0x2e; constexpr const size_t legacyPCHSensorNumber = 0x22; constexpr const char* exitAirPathName = "Exit_Air"; constexpr const char* pchPathName = "SSB_Temp"; constexpr const char* pidConfigurationIface = "xyz.openbmc_project.Configuration.Pid"; static std::string getConfigPath(const std::string& name) { std::shared_ptr dbus = getSdBus(); auto method = dbus->new_method_call("xyz.openbmc_project.ObjectMapper", "/xyz/openbmc_project/object_mapper", "xyz.openbmc_project.ObjectMapper", "GetSubTree"); method.append("/", 0, std::array{pidConfigurationIface}); std::string path; GetSubTreeType resp; try { auto reply = dbus->call(method); reply.read(resp); } catch (const sdbusplus::exception_t&) { phosphor::logging::log( "ipmiOEMGetFscParameter: mapper error"); }; auto config = std::find_if(resp.begin(), resp.end(), [&name](const auto& pair) { return pair.first.find(name) != std::string::npos; }); if (config != resp.end()) { path = std::move(config->first); } return path; } // flat map to make alphabetical static boost::container::flat_map getPidConfigs() { boost::container::flat_map ret; std::shared_ptr dbus = getSdBus(); auto method = dbus->new_method_call("xyz.openbmc_project.ObjectMapper", "/xyz/openbmc_project/object_mapper", "xyz.openbmc_project.ObjectMapper", "GetSubTree"); method.append("/", 0, std::array{pidConfigurationIface}); GetSubTreeType resp; try { auto reply = dbus->call(method); reply.read(resp); } catch (const sdbusplus::exception_t&) { phosphor::logging::log( "getFanConfigPaths: mapper error"); }; for (const auto& [path, objects] : resp) { if (objects.empty()) { continue; // should be impossible } try { ret.emplace(path, getAllDbusProperties(*dbus, objects[0].first, path, pidConfigurationIface)); } catch (const sdbusplus::exception_t& e) { phosphor::logging::log( "getPidConfigs: can't get DbusProperties!", phosphor::logging::entry("ERR=%s", e.what())); } } return ret; } ipmi::RspType ipmiOEMGetFanSpeedOffset(void) { boost::container::flat_map data = getPidConfigs(); if (data.empty()) { return ipmi::responseResponseError(); } uint8_t minOffset = std::numeric_limits::max(); for (const auto& [_, pid] : data) { auto findClass = pid.find("Class"); if (findClass == pid.end()) { phosphor::logging::log( "ipmiOEMGetFscParameter: found illegal pid " "configurations"); return ipmi::responseResponseError(); } std::string type = std::get(findClass->second); if (type == "fan") { auto findOutLimit = pid.find("OutLimitMin"); if (findOutLimit == pid.end()) { phosphor::logging::log( "ipmiOEMGetFscParameter: found illegal pid " "configurations"); return ipmi::responseResponseError(); } // get the min out of all the offsets minOffset = std::min( minOffset, static_cast(std::get(findOutLimit->second))); } } if (minOffset == std::numeric_limits::max()) { phosphor::logging::log( "ipmiOEMGetFscParameter: found no fan configurations!"); return ipmi::responseResponseError(); } return ipmi::responseSuccess(minOffset); } ipmi::RspType<> ipmiOEMSetFanSpeedOffset(uint8_t offset) { constexpr uint8_t maxFanSpeedOffset = 100; if (offset > maxFanSpeedOffset) { phosphor::logging::log( "ipmiOEMSetFanSpeedOffset: fan offset greater than limit"); return ipmi::responseInvalidFieldRequest(); } boost::container::flat_map data = getPidConfigs(); if (data.empty()) { phosphor::logging::log( "ipmiOEMSetFanSpeedOffset: found no pid configurations!"); return ipmi::responseResponseError(); } std::shared_ptr dbus = getSdBus(); bool found = false; for (const auto& [path, pid] : data) { auto findClass = pid.find("Class"); if (findClass == pid.end()) { phosphor::logging::log( "ipmiOEMSetFanSpeedOffset: found illegal pid " "configurations"); return ipmi::responseResponseError(); } std::string type = std::get(findClass->second); if (type == "fan") { auto findOutLimit = pid.find("OutLimitMin"); if (findOutLimit == pid.end()) { phosphor::logging::log( "ipmiOEMSetFanSpeedOffset: found illegal pid " "configurations"); return ipmi::responseResponseError(); } ipmi::setDbusProperty(*dbus, "xyz.openbmc_project.EntityManager", path, pidConfigurationIface, "OutLimitMin", static_cast(offset)); found = true; } } if (!found) { phosphor::logging::log( "ipmiOEMSetFanSpeedOffset: set no fan offsets"); return ipmi::responseResponseError(); } return ipmi::responseSuccess(); } ipmi::RspType<> ipmiOEMSetFscParameter(uint8_t command, uint8_t param1, uint8_t param2) { constexpr const size_t disableLimiting = 0x0; std::shared_ptr dbus = getSdBus(); if (command == static_cast(setFscParamFlags::tcontrol)) { std::string pathName; if (param1 == legacyExitAirSensorNumber) { pathName = exitAirPathName; } else if (param1 == legacyPCHSensorNumber) { pathName = pchPathName; } else { return ipmi::responseParmOutOfRange(); } std::string path = getConfigPath(pathName); ipmi::setDbusProperty(*dbus, "xyz.openbmc_project.EntityManager", path, pidConfigurationIface, "SetPoint", static_cast(param2)); return ipmi::responseSuccess(); } else if (command == static_cast(setFscParamFlags::cfm)) { uint16_t cfm = param1 | (static_cast(param2) << 8); // must be greater than 50 based on eps if (cfm < 50 && cfm != disableLimiting) { return ipmi::responseParmOutOfRange(); } try { ipmi::setDbusProperty(*dbus, settingsBusName, cfmLimitSettingPath, cfmLimitIface, "Limit", static_cast(cfm)); } catch (const sdbusplus::exception_t& e) { phosphor::logging::log( "ipmiOEMSetFscParameter: can't set cfm setting!", phosphor::logging::entry("ERR=%s", e.what())); return ipmi::responseResponseError(); } return ipmi::responseSuccess(); } else if (command == static_cast(setFscParamFlags::maxPwm)) { uint8_t requestedDomainMask = param1; boost::container::flat_map data = getPidConfigs(); if (data.empty()) { phosphor::logging::log( "ipmiOEMSetFscParameter: found no pid configurations!"); return ipmi::responseResponseError(); } size_t count = 0; for (const auto& [path, pid] : data) { auto findClass = pid.find("Class"); if (findClass == pid.end()) { phosphor::logging::log( "ipmiOEMSetFscParameter: found illegal pid " "configurations"); return ipmi::responseResponseError(); } std::string type = std::get(findClass->second); if (type == "fan") { if (requestedDomainMask & (1 << count)) { ipmi::setDbusProperty( *dbus, "xyz.openbmc_project.EntityManager", path, pidConfigurationIface, "OutLimitMax", static_cast(param2)); } count++; } } return ipmi::responseSuccess(); } else { // todo other command parts possibly // tcontrol is handled in peci now // fan speed offset not implemented yet // domain pwm limit not implemented return ipmi::responseParmOutOfRange(); } } ipmi::RspType< std::variant, std::array>> ipmiOEMGetFscParameter(uint8_t command, std::optional param) { constexpr uint8_t legacyDefaultSetpoint = -128; std::shared_ptr dbus = getSdBus(); if (command == static_cast(setFscParamFlags::tcontrol)) { if (!param) { return ipmi::responseReqDataLenInvalid(); } std::string pathName; if (*param == legacyExitAirSensorNumber) { pathName = exitAirPathName; } else if (*param == legacyPCHSensorNumber) { pathName = pchPathName; } else { return ipmi::responseParmOutOfRange(); } uint8_t setpoint = legacyDefaultSetpoint; std::string path = getConfigPath(pathName); if (path.size()) { Value val = ipmi::getDbusProperty( *dbus, "xyz.openbmc_project.EntityManager", path, pidConfigurationIface, "SetPoint"); setpoint = std::floor(std::get(val) + 0.5); } // old implementation used to return the "default" and current, we // don't make the default readily available so just make both the // same return ipmi::responseSuccess( std::array{setpoint, setpoint}); } else if (command == static_cast(setFscParamFlags::maxPwm)) { constexpr const size_t maxDomainCount = 8; if (!param) { return ipmi::responseReqDataLenInvalid(); } uint8_t requestedDomain = *param; if (requestedDomain >= maxDomainCount) { return ipmi::responseInvalidFieldRequest(); } boost::container::flat_map data = getPidConfigs(); if (data.empty()) { phosphor::logging::log( "ipmiOEMGetFscParameter: found no pid configurations!"); return ipmi::responseResponseError(); } size_t count = 0; for (const auto& [_, pid] : data) { auto findClass = pid.find("Class"); if (findClass == pid.end()) { phosphor::logging::log( "ipmiOEMGetFscParameter: found illegal pid " "configurations"); return ipmi::responseResponseError(); } std::string type = std::get(findClass->second); if (type == "fan") { if (requestedDomain == count) { auto findOutLimit = pid.find("OutLimitMax"); if (findOutLimit == pid.end()) { phosphor::logging::log( "ipmiOEMGetFscParameter: found illegal pid " "configurations"); return ipmi::responseResponseError(); } return ipmi::responseSuccess( static_cast(std::floor( std::get(findOutLimit->second) + 0.5))); } else { count++; } } } return ipmi::responseInvalidFieldRequest(); } else if (command == static_cast(setFscParamFlags::cfm)) { /* DataLen should be 1, but host is sending us an extra bit. As the previous behavior didn't seem to prevent this, ignore the check for now. if (param) { phosphor::logging::log( "ipmiOEMGetFscParameter: invalid input len!"); return IPMI_CC_REQ_DATA_LEN_INVALID; } */ Value cfmLimit; Value cfmMaximum; try { cfmLimit = ipmi::getDbusProperty(*dbus, settingsBusName, cfmLimitSettingPath, cfmLimitIface, "Limit"); cfmMaximum = ipmi::getDbusProperty( *dbus, "xyz.openbmc_project.ExitAirTempSensor", "/xyz/openbmc_project/control/MaxCFM", cfmLimitIface, "Limit"); } catch (const sdbusplus::exception_t& e) { phosphor::logging::log( "ipmiOEMGetFscParameter: can't get cfm setting!", phosphor::logging::entry("ERR=%s", e.what())); return ipmi::responseResponseError(); } double cfmMax = std::get(cfmMaximum); double cfmLim = std::get(cfmLimit); cfmLim = std::floor(cfmLim + 0.5); cfmMax = std::floor(cfmMax + 0.5); uint16_t cfmLimResp = static_cast(cfmLim); uint16_t cfmMaxResp = static_cast(cfmMax); return ipmi::responseSuccess( std::array{cfmLimResp, cfmMaxResp}); } else { // todo other command parts possibly // domain pwm limit not implemented return ipmi::responseParmOutOfRange(); } } using crConfigVariant = ipmi::DbusVariant; int setCRConfig(ipmi::Context::ptr& ctx, const std::string& property, const crConfigVariant& value, [[maybe_unused]] std::chrono::microseconds timeout = ipmi::IPMI_DBUS_TIMEOUT) { boost::system::error_code ec; ctx->bus->yield_method_call( ctx->yield, ec, "xyz.openbmc_project.PSURedundancy", "/xyz/openbmc_project/control/power_supply_redundancy", "org.freedesktop.DBus.Properties", "Set", "xyz.openbmc_project.Control.PowerSupplyRedundancy", property, value); if (ec) { phosphor::logging::log( "Failed to set dbus property to cold redundancy"); return -1; } return 0; } int getCRConfig( ipmi::Context::ptr& ctx, const std::string& property, crConfigVariant& value, const std::string& service = "xyz.openbmc_project.PSURedundancy", [[maybe_unused]] std::chrono::microseconds timeout = ipmi::IPMI_DBUS_TIMEOUT) { boost::system::error_code ec; value = ctx->bus->yield_method_call( ctx->yield, ec, service, "/xyz/openbmc_project/control/power_supply_redundancy", "org.freedesktop.DBus.Properties", "Get", "xyz.openbmc_project.Control.PowerSupplyRedundancy", property); if (ec) { phosphor::logging::log( "Failed to get dbus property to cold redundancy"); return -1; } return 0; } uint8_t getPSUCount(void) { std::shared_ptr dbus = getSdBus(); ipmi::Value num; try { num = ipmi::getDbusProperty( *dbus, "xyz.openbmc_project.PSURedundancy", "/xyz/openbmc_project/control/power_supply_redundancy", "xyz.openbmc_project.Control.PowerSupplyRedundancy", "PSUNumber"); } catch (const sdbusplus::exception_t& e) { phosphor::logging::log( "Failed to get PSUNumber property from dbus interface"); return 0; } uint8_t* pNum = std::get_if(&num); if (!pNum) { phosphor::logging::log( "Error to get PSU Number"); return 0; } return *pNum; } bool validateCRAlgo(std::vector& conf, uint8_t num) { if (conf.size() < num) { phosphor::logging::log( "Invalid PSU Ranking"); return false; } std::set confSet; for (uint8_t i = 0; i < num; i++) { if (conf[i] > num) { phosphor::logging::log( "PSU Ranking is larger than current PSU number"); return false; } confSet.emplace(conf[i]); } if (confSet.size() != num) { phosphor::logging::log( "duplicate PSU Ranking"); return false; } return true; } enum class crParameter { crStatus = 0, crFeature = 1, rotationFeature = 2, rotationAlgo = 3, rotationPeriod = 4, numOfPSU = 5, rotationRankOrderEffective = 6 }; constexpr ipmi::Cc ccParameterNotSupported = 0x80; static const constexpr uint32_t oneDay = 0x15180; static const constexpr uint32_t oneMonth = 0xf53700; static const constexpr uint8_t userSpecific = 0x01; static const constexpr uint8_t crSetCompleted = 0; ipmi::RspType ipmiOEMSetCRConfig( ipmi::Context::ptr& ctx, uint8_t parameter, ipmi::message::Payload& payload) { switch (static_cast(parameter)) { case crParameter::rotationFeature: { uint8_t param1; if (payload.unpack(param1) || !payload.fullyUnpacked()) { return ipmi::responseReqDataLenInvalid(); } // Rotation Enable can only be true or false if (param1 > 1) { return ipmi::responseInvalidFieldRequest(); } if (setCRConfig(ctx, "RotationEnabled", static_cast(param1))) { return ipmi::responseResponseError(); } break; } case crParameter::rotationAlgo: { // Rotation Algorithm can only be 0-BMC Specific or 1-User Specific std::string algoName; uint8_t param1; if (payload.unpack(param1)) { return ipmi::responseReqDataLenInvalid(); } switch (param1) { case 0: algoName = "xyz.openbmc_project.Control." "PowerSupplyRedundancy.Algo.bmcSpecific"; break; case 1: algoName = "xyz.openbmc_project.Control." "PowerSupplyRedundancy.Algo.userSpecific"; break; default: return ipmi::responseInvalidFieldRequest(); } if (setCRConfig(ctx, "RotationAlgorithm", algoName)) { return ipmi::responseResponseError(); } uint8_t numberOfPSU = getPSUCount(); if (!numberOfPSU) { return ipmi::responseResponseError(); } std::vector rankOrder; if (param1 == userSpecific) { if (payload.unpack(rankOrder) || !payload.fullyUnpacked()) { ipmi::responseReqDataLenInvalid(); } if (rankOrder.size() != numberOfPSU) { return ipmi::responseReqDataLenInvalid(); } if (!validateCRAlgo(rankOrder, numberOfPSU)) { return ipmi::responseInvalidFieldRequest(); } } else { if (rankOrder.size() > 0) { return ipmi::responseReqDataLenInvalid(); } for (uint8_t i = 1; i <= numberOfPSU; i++) { rankOrder.emplace_back(i); } } if (setCRConfig(ctx, "RotationRankOrder", rankOrder)) { return ipmi::responseResponseError(); } break; } case crParameter::rotationPeriod: { // Minimum Rotation period is One day (86400 seconds) and Max // Rotation Period is 6 month (0xf53700 seconds) uint32_t period; if (payload.unpack(period) || !payload.fullyUnpacked()) { return ipmi::responseReqDataLenInvalid(); } if ((period < oneDay) || (period > oneMonth)) { return ipmi::responseInvalidFieldRequest(); } if (setCRConfig(ctx, "PeriodOfRotation", period)) { return ipmi::responseResponseError(); } break; } default: { return ipmi::response(ccParameterNotSupported); } } return ipmi::responseSuccess(crSetCompleted); } ipmi::RspType>> ipmiOEMGetCRConfig(ipmi::Context::ptr& ctx, uint8_t parameter) { crConfigVariant value; switch (static_cast(parameter)) { case crParameter::crStatus: { if (getCRConfig(ctx, "ColdRedundancyStatus", value)) { return ipmi::responseResponseError(); } std::string* pStatus = std::get_if(&value); if (!pStatus) { phosphor::logging::log( "Error to get ColdRedundancyStatus property"); return ipmi::responseResponseError(); } namespace server = sdbusplus::xyz::openbmc_project::Control::server; auto status = server::PowerSupplyRedundancy::convertStatusFromString( *pStatus); switch (status) { case server::PowerSupplyRedundancy::Status::inProgress: return ipmi::responseSuccess(parameter, static_cast(1)); case server::PowerSupplyRedundancy::Status::completed: return ipmi::responseSuccess(parameter, static_cast(0)); default: phosphor::logging::log( "Error to get valid status"); return ipmi::responseResponseError(); } } case crParameter::crFeature: { if (getCRConfig(ctx, "PowerSupplyRedundancyEnabled", value)) { return ipmi::responseResponseError(); } bool* pResponse = std::get_if(&value); if (!pResponse) { phosphor::logging::log( "Error to get PowerSupplyRedundancyEnabled property"); return ipmi::responseResponseError(); } return ipmi::responseSuccess(parameter, static_cast(*pResponse)); } case crParameter::rotationFeature: { if (getCRConfig(ctx, "RotationEnabled", value)) { return ipmi::responseResponseError(); } bool* pResponse = std::get_if(&value); if (!pResponse) { phosphor::logging::log( "Error to get RotationEnabled property"); return ipmi::responseResponseError(); } return ipmi::responseSuccess(parameter, static_cast(*pResponse)); } case crParameter::rotationAlgo: { if (getCRConfig(ctx, "RotationAlgorithm", value)) { return ipmi::responseResponseError(); } std::string* pAlgo = std::get_if(&value); if (!pAlgo) { phosphor::logging::log( "Error to get RotationAlgorithm property"); return ipmi::responseResponseError(); } std::vector response; namespace server = sdbusplus::xyz::openbmc_project::Control::server; auto algo = server::PowerSupplyRedundancy::convertAlgoFromString(*pAlgo); switch (algo) { case server::PowerSupplyRedundancy::Algo::bmcSpecific: response.push_back(0); break; case server::PowerSupplyRedundancy::Algo::userSpecific: response.push_back(1); break; default: phosphor::logging::log( "Error to get valid algo"); return ipmi::responseResponseError(); } if (getCRConfig(ctx, "RotationRankOrder", value)) { return ipmi::responseResponseError(); } std::vector* pResponse = std::get_if>(&value); if (!pResponse) { phosphor::logging::log( "Error to get RotationRankOrder property"); return ipmi::responseResponseError(); } std::copy(pResponse->begin(), pResponse->end(), std::back_inserter(response)); return ipmi::responseSuccess(parameter, response); } case crParameter::rotationPeriod: { if (getCRConfig(ctx, "PeriodOfRotation", value)) { return ipmi::responseResponseError(); } uint32_t* pResponse = std::get_if(&value); if (!pResponse) { phosphor::logging::log( "Error to get RotationAlgorithm property"); return ipmi::responseResponseError(); } return ipmi::responseSuccess(parameter, *pResponse); } case crParameter::numOfPSU: { uint8_t numberOfPSU = getPSUCount(); if (!numberOfPSU) { return ipmi::responseResponseError(); } return ipmi::responseSuccess(parameter, numberOfPSU); } case crParameter::rotationRankOrderEffective: { if (getCRConfig(ctx, "RotationRankOrder", value, "xyz.openbmc_project.PSURedundancy")) { return ipmi::responseResponseError(); } std::vector* pResponse = std::get_if>(&value); if (!pResponse) { phosphor::logging::log( "Error to get effective RotationRankOrder property"); return ipmi::responseResponseError(); } return ipmi::responseSuccess(parameter, *pResponse); } default: { return ipmi::response(ccParameterNotSupported); } } } ipmi::RspType<> ipmiOEMSetFaultIndication( uint8_t sourceId, uint8_t faultType, uint8_t faultState, uint8_t faultGroup, std::array& ledStateData) { constexpr auto maxFaultType = static_cast(RemoteFaultType::max); static const std::array faultNames = { "faultFan", "faultTemp", "faultPower", "faultDriveSlot", "faultSoftware", "faultMemory"}; constexpr uint8_t maxFaultSource = 0x4; constexpr uint8_t skipLEDs = 0xFF; constexpr uint8_t pinSize = 64; constexpr uint8_t groupSize = 16; constexpr uint8_t groupNum = 5; // 4 for fault memory, 1 for faultFan // same pin names need to be defined in dts file static const std::array, groupNum> faultLedPinNames = {{ "LED_CPU1_CH1_DIMM1_FAULT", "LED_CPU1_CH1_DIMM2_FAULT", "LED_CPU1_CH2_DIMM1_FAULT", "LED_CPU1_CH2_DIMM2_FAULT", "LED_CPU1_CH3_DIMM1_FAULT", "LED_CPU1_CH3_DIMM2_FAULT", "LED_CPU1_CH4_DIMM1_FAULT", "LED_CPU1_CH4_DIMM2_FAULT", "LED_CPU1_CH5_DIMM1_FAULT", "LED_CPU1_CH5_DIMM2_FAULT", "LED_CPU1_CH6_DIMM1_FAULT", "LED_CPU1_CH6_DIMM2_FAULT", "", "", "", "", // end of group1 "LED_CPU2_CH1_DIMM1_FAULT", "LED_CPU2_CH1_DIMM2_FAULT", "LED_CPU2_CH2_DIMM1_FAULT", "LED_CPU2_CH2_DIMM2_FAULT", "LED_CPU2_CH3_DIMM1_FAULT", "LED_CPU2_CH3_DIMM2_FAULT", "LED_CPU2_CH4_DIMM1_FAULT", "LED_CPU2_CH4_DIMM2_FAULT", "LED_CPU2_CH5_DIMM1_FAULT", "LED_CPU2_CH5_DIMM2_FAULT", "LED_CPU2_CH6_DIMM1_FAULT", "LED_CPU2_CH6_DIMM2_FAULT", "", "", "", "", // endof group2 "LED_CPU3_CH1_DIMM1_FAULT", "LED_CPU3_CH1_DIMM2_FAULT", "LED_CPU3_CH2_DIMM1_FAULT", "LED_CPU3_CH2_DIMM2_FAULT", "LED_CPU3_CH3_DIMM1_FAULT", "LED_CPU3_CH3_DIMM2_FAULT", "LED_CPU3_CH4_DIMM1_FAULT", "LED_CPU3_CH4_DIMM2_FAULT", "LED_CPU3_CH5_DIMM1_FAULT", "LED_CPU3_CH5_DIMM2_FAULT", "LED_CPU3_CH6_DIMM1_FAULT", "LED_CPU3_CH6_DIMM2_FAULT", "", "", "", "", // end of group3 "LED_CPU4_CH1_DIMM1_FAULT", "LED_CPU4_CH1_DIMM2_FAULT", "LED_CPU4_CH2_DIMM1_FAULT", "LED_CPU4_CH2_DIMM2_FAULT", "LED_CPU4_CH3_DIMM1_FAULT", "LED_CPU4_CH3_DIMM2_FAULT", "LED_CPU4_CH4_DIMM1_FAULT", "LED_CPU4_CH4_DIMM2_FAULT", "LED_CPU4_CH5_DIMM1_FAULT", "LED_CPU4_CH5_DIMM2_FAULT", "LED_CPU4_CH6_DIMM1_FAULT", "LED_CPU4_CH6_DIMM2_FAULT", "", "", "", "", // end of group4 "LED_FAN1_FAULT", "LED_FAN2_FAULT", "LED_FAN3_FAULT", "LED_FAN4_FAULT", "LED_FAN5_FAULT", "LED_FAN6_FAULT", "LED_FAN7_FAULT", "LED_FAN8_FAULT", "", "", "", "", "", "", "", "" // end of group5 }}; // Validate the source, fault type -- // (Byte 1) sourceId: Unspecified, Hot-Swap Controller 0, Hot-Swap // Controller 1, BIOS (Byte 2) fault type: fan, temperature, power, // driveslot, software, memory (Byte 3) FaultState: OK, Degraded, // Non-Critical, Critical, Non-Recoverable, (Byte 4) is faultGroup, // definition differs based on fault type (Byte 2) // Type Fan=> Group: 0=FanGroupID, FF-not used // Byte 5-11 00h, not used // Byte12 FanLedState [7:0]-Fans 7:0 // Type Memory=> Group: 0 = DIMM GroupID, FF-not used // Byte 5:12 - DIMM LED state (64bit field, LS Byte first) // [63:48] = CPU4 channels 7:0, 2 bits per channel // [47:32] = CPU3 channels 7:0, 2 bits per channel // [31:16] = CPU2 channels 7:0, 2 bits per channel // [15:0] = CPU1 channels 7:0, 2 bits per channel // Type Other=> Component Fault LED Group ID, not used set to 0xFF // Byte[5:12]: reserved 0x00h if ((sourceId >= maxFaultSource) || (faultType >= static_cast(RemoteFaultType::max)) || (faultState >= static_cast(RemoteFaultState::maxFaultState)) || (faultGroup >= static_cast(DimmFaultType::maxFaultGroup))) { return ipmi::responseParmOutOfRange(); } size_t pinGroupOffset = 0; size_t pinGroupMax = pinSize / groupSize; if (RemoteFaultType::fan == RemoteFaultType(faultType)) { pinGroupOffset = 4; pinGroupMax = groupNum - pinSize / groupSize; } switch (RemoteFaultType(faultType)) { case (RemoteFaultType::fan): case (RemoteFaultType::memory): { if (faultGroup == skipLEDs) { return ipmi::responseSuccess(); } // calculate led state bit filed count, each byte has 8bits // the maximum bits will be 8 * 8 bits constexpr uint8_t size = sizeof(ledStateData) * 8; // assemble ledState uint64_t ledState = 0; bool hasError = false; for (size_t i = 0; i < sizeof(ledStateData); i++) { ledState = (uint64_t)(ledState << 8); ledState = (uint64_t)(ledState | (uint64_t)ledStateData[i]); } std::bitset ledStateBits(ledState); for (size_t group = 0; group < pinGroupMax; group++) { for (int i = 0; i < groupSize; i++) { // skip non-existing pins if (0 == faultLedPinNames[group + pinGroupOffset][i].size()) { continue; } gpiod::line line = gpiod::find_line( faultLedPinNames[group + pinGroupOffset][i]); if (!line) { phosphor::logging::log( "Not Find Led Gpio Device!", phosphor::logging::entry( "DEVICE=%s", faultLedPinNames[group + pinGroupOffset][i] .c_str())); hasError = true; continue; } bool activeHigh = (line.active_state() == gpiod::line::ACTIVE_HIGH); try { line.request( {"faultLed", gpiod::line_request::DIRECTION_OUTPUT, activeHigh ? 0 : gpiod::line_request::FLAG_ACTIVE_LOW}); line.set_value(ledStateBits[i + group * groupSize]); } catch (const std::system_error&) { phosphor::logging::log( "Error write Led Gpio Device!", phosphor::logging::entry( "DEVICE=%s", faultLedPinNames[group + pinGroupOffset][i] .c_str())); hasError = true; continue; } } // for int i } if (hasError) { return ipmi::responseResponseError(); } break; } default: { // now only support two fault types return ipmi::responseParmOutOfRange(); } } // switch return ipmi::responseSuccess(); } ipmi::RspType ipmiOEMReadBoardProductId() { uint8_t prodId = 0; try { std::shared_ptr dbus = getSdBus(); const DbusObjectInfo& object = getDbusObject( *dbus, "xyz.openbmc_project.Inventory.Item.Board", "/xyz/openbmc_project/inventory/system/board/", "Baseboard"); const Value& propValue = getDbusProperty( *dbus, object.second, object.first, "xyz.openbmc_project.Inventory.Item.Board.Motherboard", "ProductId"); prodId = static_cast(std::get(propValue)); } catch (const std::exception& e) { phosphor::logging::log( "ipmiOEMReadBoardProductId: Product ID read failed!", phosphor::logging::entry("ERR=%s", e.what())); } return ipmi::responseSuccess(prodId); } /** @brief implements the get security mode command * @param ctx - ctx pointer * * @returns IPMI completion code with following data * - restriction mode value - As specified in * xyz.openbmc_project.Control.Security.RestrictionMode.interface.yaml * - special mode value - As specified in * xyz.openbmc_project.Control.Security.SpecialMode.interface.yaml */ ipmi::RspType ipmiGetSecurityMode(ipmi::Context::ptr& ctx) { namespace securityNameSpace = sdbusplus::xyz::openbmc_project::Control::Security::server; uint8_t restrictionModeValue = 0; uint8_t specialModeValue = 0; boost::system::error_code ec; auto varRestrMode = ctx->bus->yield_method_call( ctx->yield, ec, restricionModeService, restricionModeBasePath, dBusPropertyIntf, dBusPropertyGetMethod, restricionModeIntf, restricionModeProperty); if (ec) { phosphor::logging::log( "ipmiGetSecurityMode: failed to get RestrictionMode property", phosphor::logging::entry("ERROR=%s", ec.message().c_str())); return ipmi::responseUnspecifiedError(); } restrictionModeValue = static_cast( securityNameSpace::RestrictionMode::convertModesFromString( std::get(varRestrMode))); auto varSpecialMode = ctx->bus->yield_method_call( ctx->yield, ec, specialModeService, specialModeBasePath, dBusPropertyIntf, dBusPropertyGetMethod, specialModeIntf, specialModeProperty); if (ec) { phosphor::logging::log( "ipmiGetSecurityMode: failed to get SpecialMode property", phosphor::logging::entry("ERROR=%s", ec.message().c_str())); // fall through, let us not worry about SpecialMode property, which is // not required in user scenario } else { specialModeValue = static_cast( securityNameSpace::SpecialMode::convertModesFromString( std::get(varSpecialMode))); } return ipmi::responseSuccess(restrictionModeValue, specialModeValue); } /** @brief implements the set security mode command * Command allows to upgrade the restriction mode and won't allow * to downgrade from system interface * @param ctx - ctx pointer * @param restrictionMode - restriction mode value to be set. * * @returns IPMI completion code */ ipmi::RspType<> ipmiSetSecurityMode(ipmi::Context::ptr& ctx, uint8_t restrictionMode, std::optional specialMode) { #ifndef BMC_VALIDATION_UNSECURE_FEATURE if (specialMode) { return ipmi::responseReqDataLenInvalid(); } #endif namespace securityNameSpace = sdbusplus::xyz::openbmc_project::Control::Security::server; ChannelInfo chInfo; if (getChannelInfo(ctx->channel, chInfo) != ccSuccess) { phosphor::logging::log( "ipmiSetSecurityMode: Failed to get Channel Info", phosphor::logging::entry("CHANNEL=%d", ctx->channel)); return ipmi::responseUnspecifiedError(); } auto reqMode = static_cast(restrictionMode); if ((reqMode < securityNameSpace::RestrictionMode::Modes::Provisioning) || (reqMode > securityNameSpace::RestrictionMode::Modes::ProvisionedHostDisabled)) { return ipmi::responseInvalidFieldRequest(); } boost::system::error_code ec; auto varRestrMode = ctx->bus->yield_method_call( ctx->yield, ec, restricionModeService, restricionModeBasePath, dBusPropertyIntf, dBusPropertyGetMethod, restricionModeIntf, restricionModeProperty); if (ec) { phosphor::logging::log( "ipmiSetSecurityMode: failed to get RestrictionMode property", phosphor::logging::entry("ERROR=%s", ec.message().c_str())); return ipmi::responseUnspecifiedError(); } auto currentRestrictionMode = securityNameSpace::RestrictionMode::convertModesFromString( std::get(varRestrMode)); if (chInfo.mediumType != static_cast(EChannelMediumType::lan8032) && currentRestrictionMode > reqMode) { phosphor::logging::log( "ipmiSetSecurityMode - Downgrading security mode not supported " "through system interface", phosphor::logging::entry( "CUR_MODE=%d", static_cast(currentRestrictionMode)), phosphor::logging::entry("REQ_MODE=%d", restrictionMode)); return ipmi::responseCommandNotAvailable(); } ec.clear(); ctx->bus->yield_method_call<>( ctx->yield, ec, restricionModeService, restricionModeBasePath, dBusPropertyIntf, dBusPropertySetMethod, restricionModeIntf, restricionModeProperty, static_cast( securityNameSpace::convertForMessage(reqMode))); if (ec) { phosphor::logging::log( "ipmiSetSecurityMode: failed to set RestrictionMode property", phosphor::logging::entry("ERROR=%s", ec.message().c_str())); return ipmi::responseUnspecifiedError(); } #ifdef BMC_VALIDATION_UNSECURE_FEATURE if (specialMode) { constexpr uint8_t mfgMode = 0x01; // Manufacturing mode is reserved. So can't enable this mode. if (specialMode.value() == mfgMode) { phosphor::logging::log( "ipmiSetSecurityMode: Can't enable Manufacturing mode"); return ipmi::responseInvalidFieldRequest(); } ec.clear(); ctx->bus->yield_method_call<>( ctx->yield, ec, specialModeService, specialModeBasePath, dBusPropertyIntf, dBusPropertySetMethod, specialModeIntf, specialModeProperty, static_cast(securityNameSpace::convertForMessage( static_cast( specialMode.value())))); if (ec) { phosphor::logging::log( "ipmiSetSecurityMode: failed to set SpecialMode property", phosphor::logging::entry("ERROR=%s", ec.message().c_str())); return ipmi::responseUnspecifiedError(); } } #endif return ipmi::responseSuccess(); } ipmi::RspType ipmiRestoreConfiguration(const std::array& clr, uint8_t cmd) { static constexpr std::array expClr = {'C', 'L', 'R'}; if (clr != expClr) { return ipmi::responseInvalidFieldRequest(); } constexpr uint8_t cmdStatus = 0; constexpr uint8_t cmdDefaultRestore = 0xaa; constexpr uint8_t cmdFullRestore = 0xbb; constexpr uint8_t cmdFormat = 0xcc; constexpr const char* restoreOpFname = "/tmp/.rwfs/.restore_op"; switch (cmd) { case cmdStatus: break; case cmdDefaultRestore: case cmdFullRestore: case cmdFormat: { // write file to rwfs root int value = (cmd - 1) & 0x03; // map aa, bb, cc => 1, 2, 3 std::ofstream restoreFile(restoreOpFname); if (!restoreFile) { return ipmi::responseUnspecifiedError(); } restoreFile << value << "\n"; phosphor::logging::log( "Restore to default will be performed on next BMC boot", phosphor::logging::entry("ACTION=0x%0X", cmd)); break; } default: return ipmi::responseInvalidFieldRequest(); } constexpr uint8_t restorePending = 0; constexpr uint8_t restoreComplete = 1; uint8_t restoreStatus = std::filesystem::exists(restoreOpFname) ? restorePending : restoreComplete; return ipmi::responseSuccess(restoreStatus); } ipmi::RspType ipmiOEMGetNmiSource(void) { uint8_t bmcSource; namespace nmi = sdbusplus::xyz::openbmc_project::Chassis::Control::server; try { std::shared_ptr dbus = getSdBus(); std::string service = getService(*dbus, oemNmiSourceIntf, oemNmiSourceObjPath); Value variant = getDbusProperty(*dbus, service, oemNmiSourceObjPath, oemNmiSourceIntf, oemNmiBmcSourceObjPathProp); switch (nmi::NMISource::convertBMCSourceSignalFromString( std::get(variant))) { case nmi::NMISource::BMCSourceSignal::None: bmcSource = static_cast(NmiSource::none); break; case nmi::NMISource::BMCSourceSignal::FrontPanelButton: bmcSource = static_cast(NmiSource::frontPanelButton); break; case nmi::NMISource::BMCSourceSignal::Watchdog: bmcSource = static_cast(NmiSource::watchdog); break; case nmi::NMISource::BMCSourceSignal::ChassisCmd: bmcSource = static_cast(NmiSource::chassisCmd); break; case nmi::NMISource::BMCSourceSignal::MemoryError: bmcSource = static_cast(NmiSource::memoryError); break; case nmi::NMISource::BMCSourceSignal::PciBusError: bmcSource = static_cast(NmiSource::pciBusError); break; case nmi::NMISource::BMCSourceSignal::PCH: bmcSource = static_cast(NmiSource::pch); break; case nmi::NMISource::BMCSourceSignal::Chipset: bmcSource = static_cast(NmiSource::chipset); break; default: phosphor::logging::log( "NMI source: invalid property!", phosphor::logging::entry( "PROP=%s", std::get(variant).c_str())); return ipmi::responseResponseError(); } } catch (const sdbusplus::exception_t& e) { phosphor::logging::log(e.what()); return ipmi::responseResponseError(); } return ipmi::responseSuccess(bmcSource); } ipmi::RspType<> ipmiOEMSetNmiSource(uint8_t sourceId) { namespace nmi = sdbusplus::xyz::openbmc_project::Chassis::Control::server; nmi::NMISource::BMCSourceSignal bmcSourceSignal = nmi::NMISource::BMCSourceSignal::None; switch (NmiSource(sourceId)) { case NmiSource::none: bmcSourceSignal = nmi::NMISource::BMCSourceSignal::None; break; case NmiSource::frontPanelButton: bmcSourceSignal = nmi::NMISource::BMCSourceSignal::FrontPanelButton; break; case NmiSource::watchdog: bmcSourceSignal = nmi::NMISource::BMCSourceSignal::Watchdog; break; case NmiSource::chassisCmd: bmcSourceSignal = nmi::NMISource::BMCSourceSignal::ChassisCmd; break; case NmiSource::memoryError: bmcSourceSignal = nmi::NMISource::BMCSourceSignal::MemoryError; break; case NmiSource::pciBusError: bmcSourceSignal = nmi::NMISource::BMCSourceSignal::PciBusError; break; case NmiSource::pch: bmcSourceSignal = nmi::NMISource::BMCSourceSignal::PCH; break; case NmiSource::chipset: bmcSourceSignal = nmi::NMISource::BMCSourceSignal::Chipset; break; default: phosphor::logging::log( "NMI source: invalid property!"); return ipmi::responseResponseError(); } try { // keep NMI signal source std::shared_ptr dbus = getSdBus(); std::string service = getService(*dbus, oemNmiSourceIntf, oemNmiSourceObjPath); setDbusProperty(*dbus, service, oemNmiSourceObjPath, oemNmiSourceIntf, oemNmiBmcSourceObjPathProp, nmi::convertForMessage(bmcSourceSignal)); // set Enabled property to inform NMI source handling // to trigger a NMI_OUT BSOD. // if it's triggered by NMI source property changed, // NMI_OUT BSOD could be missed if the same source occurs twice in a row if (bmcSourceSignal != nmi::NMISource::BMCSourceSignal::None) { setDbusProperty(*dbus, service, oemNmiSourceObjPath, oemNmiSourceIntf, oemNmiEnabledObjPathProp, static_cast(true)); } } catch (const sdbusplus::exception_t& e) { phosphor::logging::log(e.what()); return ipmi::responseResponseError(); } return ipmi::responseSuccess(); } namespace dimmOffset { constexpr const char* dimmPower = "DimmPower"; constexpr const char* staticCltt = "StaticCltt"; constexpr const char* offsetPath = "/xyz/openbmc_project/Inventory/Item/Dimm"; constexpr const char* offsetInterface = "xyz.openbmc_project.Inventory.Item.Dimm.Offset"; constexpr const char* property = "DimmOffset"; }; // namespace dimmOffset ipmi::RspType<> ipmiOEMSetDimmOffset( uint8_t type, const std::vector>& data) { if (type != static_cast(dimmOffsetTypes::dimmPower) && type != static_cast(dimmOffsetTypes::staticCltt)) { return ipmi::responseInvalidFieldRequest(); } if (data.empty()) { return ipmi::responseInvalidFieldRequest(); } nlohmann::json json; std::ifstream jsonStream(dimmOffsetFile); if (jsonStream.good()) { json = nlohmann::json::parse(jsonStream, nullptr, false); if (json.is_discarded()) { json = nlohmann::json(); } jsonStream.close(); } std::string typeName; if (type == static_cast(dimmOffsetTypes::dimmPower)) { typeName = dimmOffset::dimmPower; } else { typeName = dimmOffset::staticCltt; } nlohmann::json& field = json[typeName]; for (const auto& [index, value] : data) { field[index] = value; } for (nlohmann::json& val : field) { if (val == nullptr) { val = static_cast(0); } } std::ofstream output(dimmOffsetFile); if (!output.good()) { std::cerr << "Error writing json file\n"; return ipmi::responseResponseError(); } output << json.dump(4); if (type == static_cast(dimmOffsetTypes::staticCltt)) { std::shared_ptr bus = getSdBus(); ipmi::DbusVariant offsets = field.get>(); auto call = bus->new_method_call( settingsBusName, dimmOffset::offsetPath, PROP_INTF, "Set"); call.append(dimmOffset::offsetInterface, dimmOffset::property, offsets); try { bus->call(call); } catch (const sdbusplus::exception_t& e) { phosphor::logging::log( "ipmiOEMSetDimmOffset: can't set dimm offsets!", phosphor::logging::entry("ERR=%s", e.what())); return ipmi::responseResponseError(); } } return ipmi::responseSuccess(); } ipmi::RspType ipmiOEMGetDimmOffset(uint8_t type, uint8_t index) { if (type != static_cast(dimmOffsetTypes::dimmPower) && type != static_cast(dimmOffsetTypes::staticCltt)) { return ipmi::responseInvalidFieldRequest(); } std::ifstream jsonStream(dimmOffsetFile); auto json = nlohmann::json::parse(jsonStream, nullptr, false); if (json.is_discarded()) { std::cerr << "File error in " << dimmOffsetFile << "\n"; return ipmi::responseResponseError(); } std::string typeName; if (type == static_cast(dimmOffsetTypes::dimmPower)) { typeName = dimmOffset::dimmPower; } else { typeName = dimmOffset::staticCltt; } auto it = json.find(typeName); if (it == json.end()) { return ipmi::responseInvalidFieldRequest(); } if (it->size() <= index) { return ipmi::responseInvalidFieldRequest(); } uint8_t resp = it->at(index).get(); return ipmi::responseSuccess(resp); } namespace boot_options { using namespace sdbusplus::xyz::openbmc_project::Control::Boot::server; using IpmiValue = uint8_t; constexpr auto ipmiDefault = 0; std::map sourceIpmiToDbus = { {0x01, Source::Sources::Network}, {0x02, Source::Sources::Disk}, {0x05, Source::Sources::ExternalMedia}, {0x0f, Source::Sources::RemovableMedia}, {ipmiDefault, Source::Sources::Default}}; std::map modeIpmiToDbus = { {0x06, Mode::Modes::Setup}, {ipmiDefault, Mode::Modes::Regular}}; std::map sourceDbusToIpmi = { {Source::Sources::Network, 0x01}, {Source::Sources::Disk, 0x02}, {Source::Sources::ExternalMedia, 0x05}, {Source::Sources::RemovableMedia, 0x0f}, {Source::Sources::Default, ipmiDefault}}; std::map modeDbusToIpmi = { {Mode::Modes::Setup, 0x06}, {Mode::Modes::Regular, ipmiDefault}}; static constexpr auto bootModeIntf = "xyz.openbmc_project.Control.Boot.Mode"; static constexpr auto bootSourceIntf = "xyz.openbmc_project.Control.Boot.Source"; static constexpr auto enabledIntf = "xyz.openbmc_project.Object.Enable"; static constexpr auto persistentObjPath = "/xyz/openbmc_project/control/host0/boot"; static constexpr auto oneTimePath = "/xyz/openbmc_project/control/host0/boot/one_time"; static constexpr auto bootSourceProp = "BootSource"; static constexpr auto bootModeProp = "BootMode"; static constexpr auto oneTimeBootEnableProp = "Enabled"; static constexpr auto httpBootMode = "xyz.openbmc_project.Control.Boot.Source.Sources.Http"; enum class BootOptionParameter : size_t { setInProgress = 0x0, bootFlags = 0x5, }; static constexpr uint8_t setComplete = 0x0; static constexpr uint8_t setInProgress = 0x1; static uint8_t transferStatus = setComplete; static constexpr uint8_t setParmVersion = 0x01; static constexpr uint8_t setParmBootFlagsPermanent = 0x40; static constexpr uint8_t setParmBootFlagsValidOneTime = 0x80; static constexpr uint8_t setParmBootFlagsValidPermanent = 0xC0; static constexpr uint8_t httpBoot = 0xd; static constexpr uint8_t bootSourceMask = 0x3c; } // namespace boot_options ipmi::RspType // data1, dependent on parameter > ipmiOemGetEfiBootOptions(uint8_t parameter, [[maybe_unused]] uint8_t set, [[maybe_unused]] uint8_t block) { using namespace boot_options; uint8_t bootOption = 0; if (parameter == static_cast(BootOptionParameter::setInProgress)) { return ipmi::responseSuccess(setParmVersion, parameter, transferStatus, std::nullopt); } if (parameter != static_cast(BootOptionParameter::bootFlags)) { phosphor::logging::log( "Unsupported parameter"); return ipmi::response(ccParameterNotSupported); } try { auto oneTimeEnabled = false; // read one time Enabled property std::shared_ptr dbus = getSdBus(); std::string service = getService(*dbus, enabledIntf, oneTimePath); Value variant = getDbusProperty(*dbus, service, oneTimePath, enabledIntf, oneTimeBootEnableProp); oneTimeEnabled = std::get(variant); // get BootSource and BootMode properties // according to oneTimeEnable auto bootObjPath = oneTimePath; if (oneTimeEnabled == false) { bootObjPath = persistentObjPath; } service = getService(*dbus, bootModeIntf, bootObjPath); variant = getDbusProperty(*dbus, service, bootObjPath, bootModeIntf, bootModeProp); auto bootMode = Mode::convertModesFromString(std::get(variant)); service = getService(*dbus, bootSourceIntf, bootObjPath); variant = getDbusProperty(*dbus, service, bootObjPath, bootSourceIntf, bootSourceProp); if (std::get(variant) == httpBootMode) { bootOption = httpBoot; } else { auto bootSource = Source::convertSourcesFromString( std::get(variant)); bootOption = sourceDbusToIpmi.at(bootSource); if (Source::Sources::Default == bootSource) { bootOption = modeDbusToIpmi.at(bootMode); } } uint8_t oneTime = oneTimeEnabled ? setParmBootFlagsValidOneTime : setParmBootFlagsValidPermanent; bootOption <<= 2; // shift for responseconstexpr return ipmi::responseSuccess(setParmVersion, parameter, oneTime, bootOption); } catch (const sdbusplus::exception_t& e) { phosphor::logging::log(e.what()); return ipmi::responseResponseError(); } } ipmi::RspType<> ipmiOemSetEfiBootOptions(uint8_t bootFlag, uint8_t bootParam, std::optional bootOption) { using namespace boot_options; auto oneTimeEnabled = false; if (bootFlag == 0 && bootParam == 0) { phosphor::logging::log( "Unsupported parameter"); return ipmi::response(ccParameterNotSupported); } if (bootFlag == static_cast(BootOptionParameter::setInProgress)) { if (bootOption) { return ipmi::responseReqDataLenInvalid(); } if (transferStatus == setInProgress) { phosphor::logging::log( "boot option set in progress!"); return ipmi::responseResponseError(); } transferStatus = bootParam; return ipmi::responseSuccess(); } if (bootFlag != (uint8_t)BootOptionParameter::bootFlags) { phosphor::logging::log( "Unsupported parameter"); return ipmi::response(ccParameterNotSupported); } if (!bootOption) { return ipmi::responseReqDataLenInvalid(); } if (((bootOption.value() & bootSourceMask) >> 2) != httpBoot) // not http boot, exit { phosphor::logging::log( "wrong boot option parameter!"); return ipmi::responseParmOutOfRange(); } try { bool permanent = (bootParam & setParmBootFlagsPermanent) == setParmBootFlagsPermanent; // read one time Enabled property std::shared_ptr dbus = getSdBus(); std::string service = getService(*dbus, enabledIntf, oneTimePath); Value variant = getDbusProperty(*dbus, service, oneTimePath, enabledIntf, oneTimeBootEnableProp); oneTimeEnabled = std::get(variant); /* * Check if the current boot setting is onetime or permanent, if the * request in the command is otherwise, then set the "Enabled" * property in one_time object path to 'True' to indicate onetime * and 'False' to indicate permanent. * * Once the onetime/permanent setting is applied, then the bootMode * and bootSource is updated for the corresponding object. */ if (permanent == oneTimeEnabled) { setDbusProperty(*dbus, service, oneTimePath, enabledIntf, oneTimeBootEnableProp, !permanent); } // set BootSource and BootMode properties // according to oneTimeEnable or persistent auto bootObjPath = oneTimePath; if (oneTimeEnabled == false) { bootObjPath = persistentObjPath; } std::string bootMode = "xyz.openbmc_project.Control.Boot.Mode.Modes.Regular"; std::string bootSource = httpBootMode; service = getService(*dbus, bootModeIntf, bootObjPath); setDbusProperty(*dbus, service, bootObjPath, bootModeIntf, bootModeProp, bootMode); service = getService(*dbus, bootSourceIntf, bootObjPath); setDbusProperty(*dbus, service, bootObjPath, bootSourceIntf, bootSourceProp, bootSource); } catch (const sdbusplus::exception_t& e) { phosphor::logging::log(e.what()); return ipmi::responseResponseError(); } return ipmi::responseSuccess(); } using BasicVariantType = ipmi::DbusVariant; using PropertyMapType = boost::container::flat_map; static constexpr const std::array psuPresenceTypes = { "xyz.openbmc_project.Configuration.PSUPresence"}; int getPSUAddress(ipmi::Context::ptr& ctx, uint8_t& bus, std::vector& addrTable) { boost::system::error_code ec; GetSubTreeType subtree = ctx->bus->yield_method_call( ctx->yield, ec, "xyz.openbmc_project.ObjectMapper", "/xyz/openbmc_project/object_mapper", "xyz.openbmc_project.ObjectMapper", "GetSubTree", "/xyz/openbmc_project/inventory/system", 3, psuPresenceTypes); if (ec) { phosphor::logging::log( "Failed to set dbus property to cold redundancy"); return -1; } for (const auto& object : subtree) { std::string pathName = object.first; for (const auto& serviceIface : object.second) { std::string serviceName = serviceIface.first; ec.clear(); PropertyMapType propMap = ctx->bus->yield_method_call( ctx->yield, ec, serviceName, pathName, "org.freedesktop.DBus.Properties", "GetAll", "xyz.openbmc_project.Configuration.PSUPresence"); if (ec) { phosphor::logging::log( "Failed to set dbus property to cold redundancy"); return -1; } auto psuBus = std::get_if(&propMap["Bus"]); auto psuAddress = std::get_if>(&propMap["Address"]); if (psuBus == nullptr || psuAddress == nullptr) { std::cerr << "error finding necessary " "entry in configuration\n"; return -1; } bus = static_cast(*psuBus); addrTable = *psuAddress; return 0; } } return -1; } static const constexpr uint8_t addrOffset = 8; static const constexpr uint8_t psuRevision = 0xd9; static const constexpr uint8_t defaultPSUBus = 7; // Second Minor, Primary Minor, Major static const constexpr size_t verLen = 3; ipmi::RspType> ipmiOEMGetPSUVersion(ipmi::Context::ptr& ctx) { uint8_t bus = defaultPSUBus; std::vector addrTable; std::vector result; if (getPSUAddress(ctx, bus, addrTable)) { std::cerr << "Failed to get PSU bus and address\n"; return ipmi::responseResponseError(); } for (const auto& targetAddr : addrTable) { std::vector writeData = {psuRevision}; std::vector readBuf(verLen); uint8_t addr = static_cast(targetAddr) + addrOffset; std::string i2cBus = "/dev/i2c-" + std::to_string(bus); auto retI2C = ipmi::i2cWriteRead(i2cBus, addr, writeData, readBuf); if (retI2C != ipmi::ccSuccess) { for (size_t idx = 0; idx < verLen; idx++) { result.emplace_back(0x00); } } else { for (const uint8_t& data : readBuf) { result.emplace_back(data); } } } return ipmi::responseSuccess(result); } std::optional getMultiNodeInfoPresence(ipmi::Context::ptr& ctx, const std::string& name) { Value dbusValue = 0; std::string serviceName; boost::system::error_code ec = ipmi::getService(ctx, multiNodeIntf, multiNodeObjPath, serviceName); if (ec) { phosphor::logging::log( "Failed to perform Multinode getService."); return std::nullopt; } ec = ipmi::getDbusProperty(ctx, serviceName, multiNodeObjPath, multiNodeIntf, name, dbusValue); if (ec) { phosphor::logging::log( "Failed to perform Multinode get property"); return std::nullopt; } auto multiNodeVal = std::get_if(&dbusValue); if (!multiNodeVal) { phosphor::logging::log( "getMultiNodeInfoPresence: error to get multinode"); return std::nullopt; } return *multiNodeVal; } /** @brief implements OEM get reading command * @param domain ID * @param reading Type * - 00h = platform Power Consumption * - 01h = inlet Air Temp * - 02h = icc_TDC from PECI * @param reserved, write as 0000h * * @returns IPMI completion code plus response data * - response * - domain ID * - reading Type * - 00h = platform Power Consumption * - 01h = inlet Air Temp * - 02h = icc_TDC from PECI * - reading */ ipmi::RspType ipmiOEMGetReading(ipmi::Context::ptr& ctx, uint4_t domainId, uint4_t readingType, uint16_t reserved) { [[maybe_unused]] constexpr uint8_t platformPower = 0; constexpr uint8_t inletAirTemp = 1; constexpr uint8_t iccTdc = 2; if ((static_cast(readingType) > iccTdc) || domainId || reserved) { return ipmi::responseInvalidFieldRequest(); } // This command should run only from multi-node product. // For all other platforms this command will return invalid. std::optional nodeInfo = getMultiNodeInfoPresence(ctx, "NodePresence"); if (!nodeInfo || !*nodeInfo) { return ipmi::responseInvalidCommand(); } uint16_t oemReadingValue = 0; if (static_cast(readingType) == inletAirTemp) { double value = 0; boost::system::error_code ec = ipmi::getDbusProperty( ctx, "xyz.openbmc_project.HwmonTempSensor", "/xyz/openbmc_project/sensors/temperature/Inlet_BRD_Temp", "xyz.openbmc_project.Sensor.Value", "Value", value); if (ec) { phosphor::logging::log( "Failed to get BMC Get OEM temperature", phosphor::logging::entry("EXCEPTION=%s", ec.message().c_str())); return ipmi::responseUnspecifiedError(); } // Take the Inlet temperature oemReadingValue = static_cast(value); } else { phosphor::logging::log( "Currently Get OEM Reading support only for Inlet Air Temp"); return ipmi::responseParmOutOfRange(); } return ipmi::responseSuccess(domainId, readingType, oemReadingValue); } /** @brief implements the maximum size of * bridgeable messages used between KCS and * IPMB interfacesget security mode command. * * @returns IPMI completion code with following data * - KCS Buffer Size (In multiples of four bytes) * - IPMB Buffer Size (In multiples of four bytes) **/ ipmi::RspType ipmiOEMGetBufferSize() { // for now this is hard coded; really this number is dependent on // the BMC kcs driver as well as the host kcs driver.... // we can't know the latter. uint8_t kcsMaxBufferSize = 63 / 4; uint8_t ipmbMaxBufferSize = 128 / 4; return ipmi::responseSuccess(kcsMaxBufferSize, ipmbMaxBufferSize); } ipmi::RspType> ipmiOEMReadPFRMailbox(ipmi::Context::ptr& ctx, const uint8_t readRegister, const uint8_t numOfBytes, uint8_t registerIdentifier) { if (!ipmi::mailbox::i2cConfigLoaded) { phosphor::logging::log( "Calling PFR Load Configuration Function to Get I2C Bus and Target " "Address "); ipmi::mailbox::loadPfrConfig(ctx, ipmi::mailbox::i2cConfigLoaded); } if (!numOfBytes && !readRegister) { phosphor::logging::log( "OEM IPMI command: Read & write count are 0 which is invalid "); return ipmi::responseInvalidFieldRequest(); } switch (registerIdentifier) { case ipmi::mailbox::registerType::fifoReadRegister: { // Check if readRegister is an FIFO read register if (ipmi::mailbox::readFifoReg.find(readRegister) == ipmi::mailbox::readFifoReg.end()) { phosphor::logging::log( "OEM IPMI command: Register is not a Read FIFO "); return ipmi::responseInvalidFieldRequest(); } phosphor::logging::log( "OEM IPMI command: Register is a Read FIFO "); ipmi::mailbox::writefifo(ipmi::mailbox::provisioningCommand, readRegister); ipmi::mailbox::writefifo(ipmi::mailbox::triggerCommand, ipmi::mailbox::flushRead); std::vector writeData = {ipmi::mailbox::readFifo}; std::vector readBuf(1); std::vector result; for (int i = 0; i < numOfBytes; i++) { ipmi::Cc ret = ipmi::i2cWriteRead(ipmi::mailbox::i2cBus, ipmi::mailbox::targetAddr, writeData, readBuf); if (ret != ipmi::ccSuccess) { return ipmi::response(ret); } else { for (const uint8_t& data : readBuf) { result.emplace_back(data); } } } return ipmi::responseSuccess(result); } case ipmi::mailbox::registerType::singleByteRegister: { phosphor::logging::log( "OEM IPMI command: Register is a Single Byte Register "); std::vector writeData = {readRegister}; std::vector readBuf(numOfBytes); ipmi::Cc ret = ipmi::i2cWriteRead(ipmi::mailbox::i2cBus, ipmi::mailbox::targetAddr, writeData, readBuf); if (ret != ipmi::ccSuccess) { return ipmi::response(ret); } return ipmi::responseSuccess(readBuf); } default: { phosphor::logging::log( "OEM IPMI command: Register identifier is not valid.It should " "be 0 " "for Single Byte Register and 1 for FIFO Read Register"); return ipmi::responseInvalidFieldRequest(); } } } static void registerOEMFunctions(void) { phosphor::logging::log( "Registering OEM commands"); registerHandler(prioOemBase, intel::netFnGeneral, intel::general::cmdGetBmcVersionString, Privilege::User, ipmiOEMGetBmcVersionString); ipmiPrintAndRegister(intel::netFnGeneral, intel::general::cmdGetChassisIdentifier, NULL, ipmiOEMGetChassisIdentifier, PRIVILEGE_USER); // get chassis identifier ipmiPrintAndRegister(intel::netFnGeneral, intel::general::cmdSetSystemGUID, NULL, ipmiOEMSetSystemGUID, PRIVILEGE_ADMIN); // set system guid // registerHandler(prioOemBase, intel::netFnGeneral, intel::general::cmdDisableBMCSystemReset, Privilege::Admin, ipmiOEMDisableBMCSystemReset); // registerHandler(prioOemBase, intel::netFnGeneral, intel::general::cmdGetBMCResetDisables, Privilege::Admin, ipmiOEMGetBMCResetDisables); ipmiPrintAndRegister(intel::netFnGeneral, intel::general::cmdSetBIOSID, NULL, ipmiOEMSetBIOSID, PRIVILEGE_ADMIN); registerHandler(prioOemBase, intel::netFnGeneral, intel::general::cmdGetOEMDeviceInfo, Privilege::User, ipmiOEMGetDeviceInfo); ipmiPrintAndRegister(intel::netFnGeneral, intel::general::cmdGetAICSlotFRUIDSlotPosRecords, NULL, ipmiOEMGetAICFRU, PRIVILEGE_USER); registerHandler(prioOpenBmcBase, intel::netFnGeneral, intel::general::cmdSendEmbeddedFWUpdStatus, Privilege::Operator, ipmiOEMSendEmbeddedFwUpdStatus); registerHandler(prioOpenBmcBase, intel::netFnApp, intel::app::cmdSlotIpmb, Privilege::Admin, ipmiOEMSlotIpmb); ipmiPrintAndRegister(intel::netFnGeneral, intel::general::cmdSetPowerRestoreDelay, NULL, ipmiOEMSetPowerRestoreDelay, PRIVILEGE_OPERATOR); ipmiPrintAndRegister(intel::netFnGeneral, intel::general::cmdGetPowerRestoreDelay, NULL, ipmiOEMGetPowerRestoreDelay, PRIVILEGE_USER); registerHandler(prioOpenBmcBase, intel::netFnGeneral, intel::general::cmdSetOEMUser2Activation, Privilege::Callback, ipmiOEMSetUser2Activation); registerHandler(prioOpenBmcBase, intel::netFnGeneral, intel::general::cmdSetSpecialUserPassword, Privilege::Callback, ipmiOEMSetSpecialUserPassword); // registerHandler(prioOemBase, intel::netFnGeneral, intel::general::cmdGetProcessorErrConfig, Privilege::User, ipmiOEMGetProcessorErrConfig); // registerHandler(prioOemBase, intel::netFnGeneral, intel::general::cmdSetProcessorErrConfig, Privilege::Admin, ipmiOEMSetProcessorErrConfig); ipmiPrintAndRegister(intel::netFnGeneral, intel::general::cmdSetShutdownPolicy, NULL, ipmiOEMSetShutdownPolicy, PRIVILEGE_ADMIN); ipmiPrintAndRegister(intel::netFnGeneral, intel::general::cmdGetShutdownPolicy, NULL, ipmiOEMGetShutdownPolicy, PRIVILEGE_ADMIN); registerHandler(prioOemBase, intel::netFnGeneral, intel::general::cmdSetFanConfig, Privilege::User, ipmiOEMSetFanConfig); registerHandler(prioOemBase, intel::netFnGeneral, intel::general::cmdGetFanConfig, Privilege::User, ipmiOEMGetFanConfig); registerHandler(prioOemBase, intel::netFnGeneral, intel::general::cmdGetFanSpeedOffset, Privilege::User, ipmiOEMGetFanSpeedOffset); registerHandler(prioOemBase, intel::netFnGeneral, intel::general::cmdSetFanSpeedOffset, Privilege::User, ipmiOEMSetFanSpeedOffset); registerHandler(prioOemBase, intel::netFnGeneral, intel::general::cmdSetFscParameter, Privilege::User, ipmiOEMSetFscParameter); registerHandler(prioOemBase, intel::netFnGeneral, intel::general::cmdGetFscParameter, Privilege::User, ipmiOEMGetFscParameter); registerHandler(prioOpenBmcBase, intel::netFnGeneral, intel::general::cmdReadBaseBoardProductId, Privilege::Admin, ipmiOEMReadBoardProductId); registerHandler(prioOemBase, intel::netFnGeneral, intel::general::cmdGetNmiStatus, Privilege::User, ipmiOEMGetNmiSource); registerHandler(prioOemBase, intel::netFnGeneral, intel::general::cmdSetNmiStatus, Privilege::Operator, ipmiOEMSetNmiSource); registerHandler(prioOemBase, intel::netFnGeneral, intel::general::cmdGetEfiBootOptions, Privilege::User, ipmiOemGetEfiBootOptions); registerHandler(prioOemBase, intel::netFnGeneral, intel::general::cmdSetEfiBootOptions, Privilege::Operator, ipmiOemSetEfiBootOptions); registerHandler(prioOemBase, intel::netFnGeneral, intel::general::cmdGetSecurityMode, Privilege::User, ipmiGetSecurityMode); registerHandler(prioOemBase, intel::netFnGeneral, intel::general::cmdSetSecurityMode, Privilege::Admin, ipmiSetSecurityMode); registerHandler(prioOemBase, intel::netFnGeneral, intel::general::cmdGetLEDStatus, Privilege::Admin, ipmiOEMGetLEDStatus); ipmiPrintAndRegister(ipmi::intel::netFnPlatform, ipmi::intel::platform::cmdCfgHostSerialPortSpeed, NULL, ipmiOEMCfgHostSerialPortSpeed, PRIVILEGE_ADMIN); registerHandler(prioOemBase, intel::netFnGeneral, intel::general::cmdSetFaultIndication, Privilege::Operator, ipmiOEMSetFaultIndication); registerHandler(prioOemBase, intel::netFnGeneral, intel::general::cmdSetColdRedundancyConfig, Privilege::User, ipmiOEMSetCRConfig); registerHandler(prioOemBase, intel::netFnGeneral, intel::general::cmdGetColdRedundancyConfig, Privilege::User, ipmiOEMGetCRConfig); registerHandler(prioOemBase, intel::netFnGeneral, intel::general::cmdRestoreConfiguration, Privilege::Admin, ipmiRestoreConfiguration); registerHandler(prioOemBase, intel::netFnGeneral, intel::general::cmdSetDimmOffset, Privilege::Operator, ipmiOEMSetDimmOffset); registerHandler(prioOemBase, intel::netFnGeneral, intel::general::cmdGetDimmOffset, Privilege::Operator, ipmiOEMGetDimmOffset); registerHandler(prioOemBase, intel::netFnGeneral, intel::general::cmdGetPSUVersion, Privilege::User, ipmiOEMGetPSUVersion); registerHandler(prioOemBase, intel::netFnGeneral, intel::general::cmdGetBufferSize, Privilege::User, ipmiOEMGetBufferSize); registerHandler(prioOemBase, intel::netFnGeneral, intel::general::cmdOEMGetReading, Privilege::User, ipmiOEMGetReading); registerHandler(prioOemBase, intel::netFnApp, intel::app::cmdPFRMailboxRead, Privilege::Admin, ipmiOEMReadPFRMailbox); } } // namespace ipmi