/* // 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 #include #include #include #include #include #include #include namespace ipmi { Manufacturing mtm; static auto revertTimeOut = std::chrono::duration_cast( std::chrono::seconds(60)); // 1 minute timeout static constexpr uint8_t slotAddressTypeBus = 0; static constexpr uint8_t slotAddressTypeUniqueid = 1; static constexpr uint8_t slotI2CMaxReadSize = 35; static constexpr const char* callbackMgrService = "xyz.openbmc_project.CallbackManager"; static constexpr const char* callbackMgrIntf = "xyz.openbmc_project.CallbackManager"; static constexpr const char* callbackMgrObjPath = "/xyz/openbmc_project/CallbackManager"; static constexpr const char* retriggerLedUpdate = "RetriggerLEDUpdate"; const static constexpr char* systemDService = "org.freedesktop.systemd1"; const static constexpr char* systemDObjPath = "/org/freedesktop/systemd1"; const static constexpr char* systemDMgrIntf = "org.freedesktop.systemd1.Manager"; const static constexpr char* pidControlService = "phosphor-pid-control.service"; static inline Cc resetMtmTimer(ipmi::Context::ptr ctx) { boost::system::error_code ec; ctx->bus->yield_method_call<>(ctx->yield, ec, specialModeService, specialModeObjPath, specialModeIntf, "ResetTimer"); if (ec) { phosphor::logging::log( "Failed to reset the manufacturing mode timer"); return ccUnspecifiedError; } return ccSuccess; } int getGpioPathForSmSignal(const SmSignalGet signal, std::string& path) { switch (signal) { case SmSignalGet::smPowerButton: path = "/xyz/openbmc_project/chassis/buttons/power"; break; case SmSignalGet::smResetButton: path = "/xyz/openbmc_project/chassis/buttons/reset"; break; case SmSignalGet::smNMIButton: path = "/xyz/openbmc_project/chassis/buttons/nmi"; break; case SmSignalGet::smIdentifyButton: path = "/xyz/openbmc_project/chassis/buttons/id"; break; default: return -1; break; } return 0; } ipmi_ret_t ledStoreAndSet(SmSignalSet signal, const std::string& setState) { LedProperty* ledProp = mtm.findLedProperty(signal); if (ledProp == nullptr) { return IPMI_CC_INVALID_FIELD_REQUEST; } std::string ledName = ledProp->getName(); std::string ledService = ledServicePrefix + ledName; std::string ledPath = ledPathPrefix + ledName; ipmi::Value presentState; if (false == ledProp->getLock()) { if (mtm.getProperty(ledService.c_str(), ledPath.c_str(), ledIntf, "State", &presentState) != 0) { return IPMI_CC_UNSPECIFIED_ERROR; } ledProp->setPrevState(std::get(presentState)); ledProp->setLock(true); if (signal == SmSignalSet::smPowerFaultLed || signal == SmSignalSet::smSystemReadyLed) { mtm.revertLedCallback = true; } } if (mtm.setProperty(ledService, ledPath, ledIntf, "State", ledStateStr + setState) != 0) { return IPMI_CC_UNSPECIFIED_ERROR; } return IPMI_CC_OK; } ipmi_ret_t ledRevert(SmSignalSet signal) { LedProperty* ledProp = mtm.findLedProperty(signal); if (ledProp == nullptr) { return IPMI_CC_INVALID_FIELD_REQUEST; } if (true == ledProp->getLock()) { ledProp->setLock(false); if (signal == SmSignalSet::smPowerFaultLed || signal == SmSignalSet::smSystemReadyLed) { try { ipmi::method_no_args::callDbusMethod( *getSdBus(), callbackMgrService, callbackMgrObjPath, callbackMgrIntf, retriggerLedUpdate); } catch (sdbusplus::exception_t& e) { return IPMI_CC_UNSPECIFIED_ERROR; } mtm.revertLedCallback = false; } else { std::string ledName = ledProp->getName(); std::string ledService = ledServicePrefix + ledName; std::string ledPath = ledPathPrefix + ledName; if (mtm.setProperty(ledService, ledPath, ledIntf, "State", ledProp->getPrevState()) != 0) { return IPMI_CC_UNSPECIFIED_ERROR; } } } return IPMI_CC_OK; } void Manufacturing::initData() { ledPropertyList.push_back( LedProperty(SmSignalSet::smPowerFaultLed, "status_amber")); ledPropertyList.push_back( LedProperty(SmSignalSet::smSystemReadyLed, "status_green")); ledPropertyList.push_back( LedProperty(SmSignalSet::smIdentifyLed, "identify")); } void Manufacturing::revertTimerHandler() { #ifdef BMC_VALIDATION_UNSECURE_FEATURE if (mtm.getMfgMode() == SpecialMode::valUnsecure) { // Don't revert the behaviour for validation unsecure mode. return; } #endif if (revertFanPWM) { revertFanPWM = false; disablePidControlService(false); } if (mtmTestBeepFd != -1) { ::close(mtmTestBeepFd); mtmTestBeepFd = -1; } for (const auto& ledProperty : ledPropertyList) { const std::string& ledName = ledProperty.getName(); if (ledName == "identify" && mtm.getMfgMode() == SpecialMode::mfg) { // Don't revert the behaviour for manufacturing mode continue; } ledRevert(ledProperty.getSignal()); } } Manufacturing::Manufacturing() : revertTimer([&](void) { revertTimerHandler(); }) { initData(); } int8_t Manufacturing::getProperty(const std::string& service, const std::string& path, const std::string& interface, const std::string& propertyName, ipmi::Value* reply) { try { *reply = ipmi::getDbusProperty(*getSdBus(), service, path, interface, propertyName); } catch (const sdbusplus::exception::SdBusError& e) { phosphor::logging::log( "ERROR: getProperty"); return -1; } return 0; } int8_t Manufacturing::setProperty(const std::string& service, const std::string& path, const std::string& interface, const std::string& propertyName, ipmi::Value value) { try { ipmi::setDbusProperty(*getSdBus(), service, path, interface, propertyName, value); } catch (const sdbusplus::exception::SdBusError& e) { phosphor::logging::log( "ERROR: setProperty"); return -1; } return 0; } int8_t Manufacturing::disablePidControlService(const bool disable) { try { auto dbus = getSdBus(); auto method = dbus->new_method_call(systemDService, systemDObjPath, systemDMgrIntf, disable ? "StopUnit" : "StartUnit"); method.append(pidControlService, "replace"); auto reply = dbus->call(method); } catch (const sdbusplus::exception::SdBusError& e) { phosphor::logging::log( "ERROR: phosphor-pid-control service start or stop failed"); return -1; } return 0; } ipmi::RspType // Fan tach value > appMTMGetSignal(ipmi::Context::ptr ctx, uint8_t signalTypeByte, uint8_t instance, uint8_t actionByte) { // mfg filter logic is used to allow MTM get signal command only in // manfacturing mode. SmSignalGet signalType = static_cast(signalTypeByte); SmActionGet action = static_cast(actionByte); switch (signalType) { case SmSignalGet::smChassisIntrusion: { ipmi::Value reply; if (mtm.getProperty(intrusionService, intrusionPath, intrusionIntf, "Status", &reply) < 0) { return ipmi::responseInvalidFieldRequest(); } std::string* intrusionStatus = std::get_if(&reply); if (!intrusionStatus) { return ipmi::responseUnspecifiedError(); } uint8_t status = 0; if (!intrusionStatus->compare("Normal")) { status = static_cast(IntrusionStatus::normal); } else if (!intrusionStatus->compare("HardwareIntrusion")) { status = static_cast(IntrusionStatus::hardwareIntrusion); } else if (!intrusionStatus->compare("TamperingDetected")) { status = static_cast(IntrusionStatus::tamperingDetected); } else { return ipmi::responseUnspecifiedError(); } return ipmi::responseSuccess(status, std::nullopt); } case SmSignalGet::smFanPwmGet: { ipmi::Value reply; std::string fullPath = fanPwmPath + std::to_string(instance + 1); if (mtm.getProperty(fanService, fullPath, fanIntf, "Value", &reply) < 0) { return ipmi::responseInvalidFieldRequest(); } double* doubleVal = std::get_if(&reply); if (doubleVal == nullptr) { return ipmi::responseUnspecifiedError(); } uint8_t sensorVal = std::round(*doubleVal); resetMtmTimer(ctx); return ipmi::responseSuccess(sensorVal, std::nullopt); } break; case SmSignalGet::smFanTachometerGet: { boost::system::error_code ec; using objFlatMap = boost::container::flat_map< std::string, boost::container::flat_map< std::string, std::vector>>; auto flatMap = ctx->bus->yield_method_call( ctx->yield, ec, "xyz.openbmc_project.ObjectMapper", "/xyz/openbmc_project/object_mapper", "xyz.openbmc_project.ObjectMapper", "GetSubTree", fanTachBasePath, 0, std::array{fanIntf}); if (ec) { phosphor::logging::log( "Failed to query fan tach sub tree objects"); return ipmi::responseUnspecifiedError(); } if (instance >= flatMap.size()) { return ipmi::responseInvalidFieldRequest(); } auto itr = flatMap.nth(instance); ipmi::Value reply; if (mtm.getProperty(fanService, itr->first, fanIntf, "Value", &reply) < 0) { return ipmi::responseInvalidFieldRequest(); } double* doubleVal = std::get_if(&reply); if (doubleVal == nullptr) { return ipmi::responseUnspecifiedError(); } uint8_t sensorVal = FAN_PRESENT | FAN_SENSOR_PRESENT; std::optional fanTach = std::round(*doubleVal); resetMtmTimer(ctx); return ipmi::responseSuccess(sensorVal, fanTach); } break; case SmSignalGet::smIdentifyButton: { if (action == SmActionGet::revert || action == SmActionGet::ignore) { // ButtonMasked property is not supported for ID button as it is // unnecessary. Hence if requested for revert / ignore, override // it to sample action to make tools happy. action = SmActionGet::sample; } // fall-through } case SmSignalGet::smResetButton: case SmSignalGet::smPowerButton: case SmSignalGet::smNMIButton: { std::string path; if (getGpioPathForSmSignal(signalType, path) < 0) { return ipmi::responseInvalidFieldRequest(); } switch (action) { case SmActionGet::sample: phosphor::logging::log( "case SmActionGet::sample"); break; case SmActionGet::ignore: { phosphor::logging::log( "case SmActionGet::ignore"); if (mtm.setProperty(buttonService, path, buttonIntf, "ButtonMasked", true) < 0) { return ipmi::responseUnspecifiedError(); } } break; case SmActionGet::revert: { phosphor::logging::log( "case SmActionGet::revert"); if (mtm.setProperty(buttonService, path, buttonIntf, "ButtonMasked", false) < 0) { return ipmi::responseUnspecifiedError(); } } break; default: return ipmi::responseInvalidFieldRequest(); break; } ipmi::Value reply; if (mtm.getProperty(buttonService, path, buttonIntf, "ButtonPressed", &reply) < 0) { return ipmi::responseUnspecifiedError(); } bool* valPtr = std::get_if(&reply); if (valPtr == nullptr) { return ipmi::responseUnspecifiedError(); } resetMtmTimer(ctx); uint8_t sensorVal = *valPtr; return ipmi::responseSuccess(sensorVal, std::nullopt); } break; case SmSignalGet::smNcsiDiag: { constexpr const char* netBasePath = "/sys/class/net/eth"; constexpr const char* carrierSuffix = "/carrier"; std::ifstream netIfs(netBasePath + std::to_string(instance) + carrierSuffix); if (!netIfs.good()) { return ipmi::responseInvalidFieldRequest(); } std::string carrier; netIfs >> carrier; resetMtmTimer(ctx); return ipmi::responseSuccess( static_cast(std::stoi(carrier)), std::nullopt); } break; default: return ipmi::responseInvalidFieldRequest(); break; } } ipmi::RspType<> appMTMSetSignal(ipmi::Context::ptr ctx, uint8_t signalTypeByte, uint8_t instance, uint8_t actionByte, std::optional pwmSpeed) { // mfg filter logic is used to allow MTM set signal command only in // manfacturing mode. SmSignalSet signalType = static_cast(signalTypeByte); SmActionSet action = static_cast(actionByte); Cc retCode = ccSuccess; int8_t ret = 0; switch (signalType) { case SmSignalSet::smPowerFaultLed: case SmSignalSet::smSystemReadyLed: case SmSignalSet::smIdentifyLed: switch (action) { case SmActionSet::forceDeasserted: { phosphor::logging::log( "case SmActionSet::forceDeasserted"); retCode = ledStoreAndSet(signalType, std::string("Off")); if (retCode != ccSuccess) { return ipmi::response(retCode); } mtm.revertTimer.start(revertTimeOut); } break; case SmActionSet::forceAsserted: { phosphor::logging::log( "case SmActionSet::forceAsserted"); retCode = ledStoreAndSet(signalType, std::string("On")); if (retCode != ccSuccess) { return ipmi::response(retCode); } mtm.revertTimer.start(revertTimeOut); if (SmSignalSet::smPowerFaultLed == signalType) { // Deassert "system ready" retCode = ledStoreAndSet(SmSignalSet::smSystemReadyLed, std::string("Off")); } else if (SmSignalSet::smSystemReadyLed == signalType) { // Deassert "fault led" retCode = ledStoreAndSet(SmSignalSet::smPowerFaultLed, std::string("Off")); } } break; case SmActionSet::revert: { phosphor::logging::log( "case SmActionSet::revert"); retCode = ledRevert(signalType); } break; default: { return ipmi::responseInvalidFieldRequest(); } } break; case SmSignalSet::smFanPowerSpeed: { if ((action == SmActionSet::forceAsserted) && (!pwmSpeed)) { return ipmi::responseReqDataLenInvalid(); } if ((action == SmActionSet::forceAsserted) && (*pwmSpeed > 100)) { return ipmi::responseInvalidFieldRequest(); } uint8_t pwmValue = 0; switch (action) { case SmActionSet::revert: { if (mtm.revertFanPWM) { ret = mtm.disablePidControlService(false); if (ret < 0) { return ipmi::responseUnspecifiedError(); } mtm.revertFanPWM = false; } } break; case SmActionSet::forceAsserted: { pwmValue = *pwmSpeed; } // fall-through case SmActionSet::forceDeasserted: { if (!mtm.revertFanPWM) { ret = mtm.disablePidControlService(true); if (ret < 0) { return ipmi::responseUnspecifiedError(); } mtm.revertFanPWM = true; } mtm.revertTimer.start(revertTimeOut); std::string fanPwmInstancePath = fanPwmPath + std::to_string(instance + 1); ret = mtm.setProperty(fanService, fanPwmInstancePath, fanIntf, "Value", static_cast(pwmValue)); if (ret < 0) { return ipmi::responseUnspecifiedError(); } } break; default: { return ipmi::responseInvalidFieldRequest(); } } } break; case SmSignalSet::smSpeaker: { phosphor::logging::log( "Performing Speaker SmActionSet", phosphor::logging::entry("ACTION=%d", static_cast(action))); switch (action) { case SmActionSet::forceAsserted: { char beepDevName[] = "/dev/input/event0"; if (mtm.mtmTestBeepFd != -1) { phosphor::logging::log( "mtm beep device is opened already!"); // returning success as already beep is in progress return ipmi::response(retCode); } if ((mtm.mtmTestBeepFd = ::open(beepDevName, O_RDWR | O_CLOEXEC)) < 0) { phosphor::logging::log( "Failed to open input device"); return ipmi::responseUnspecifiedError(); } struct input_event event; event.type = EV_SND; event.code = SND_TONE; event.value = 2000; if (::write(mtm.mtmTestBeepFd, &event, sizeof(struct input_event)) != sizeof(struct input_event)) { phosphor::logging::log( "Failed to write a tone sound event"); ::close(mtm.mtmTestBeepFd); mtm.mtmTestBeepFd = -1; return ipmi::responseUnspecifiedError(); } mtm.revertTimer.start(revertTimeOut); } break; case SmActionSet::revert: case SmActionSet::forceDeasserted: { if (mtm.mtmTestBeepFd != -1) { ::close(mtm.mtmTestBeepFd); mtm.mtmTestBeepFd = -1; } } break; default: { return ipmi::responseInvalidFieldRequest(); } } } break; case SmSignalSet::smDiskFaultLed: { boost::system::error_code ec; using objPaths = std::vector; std::string driveBasePath = "/xyz/openbmc_project/inventory/item/drive/"; static constexpr const char* driveLedIntf = "xyz.openbmc_project.Led.Group"; static constexpr const char* hsbpService = "xyz.openbmc_project.HsbpManager"; auto driveList = ctx->bus->yield_method_call( ctx->yield, ec, "xyz.openbmc_project.ObjectMapper", "/xyz/openbmc_project/object_mapper", "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", driveBasePath, 0, std::array{driveLedIntf}); if (ec) { phosphor::logging::log( "Failed to query HSBP drive sub tree objects"); return ipmi::responseUnspecifiedError(); } std::string driveObjPath = driveBasePath + "Drive_" + std::to_string(instance + 1); if (std::find(driveList.begin(), driveList.end(), driveObjPath) == driveList.end()) { return ipmi::responseInvalidFieldRequest(); } bool driveLedState = false; switch (action) { case SmActionSet::forceAsserted: { driveLedState = true; } break; case SmActionSet::revert: { driveLedState = false; } break; case SmActionSet::forceDeasserted: { driveLedState = false; } break; default: { return ipmi::responseInvalidFieldRequest(); } } ret = mtm.setProperty(hsbpService, driveObjPath, driveLedIntf, "Asserted", driveLedState); if (ret < 0) { return ipmi::responseUnspecifiedError(); } } break; default: { return ipmi::responseInvalidFieldRequest(); } } if (retCode == ccSuccess) { resetMtmTimer(ctx); } return ipmi::response(retCode); } ipmi::RspType<> mtmKeepAlive(ipmi::Context::ptr ctx, uint8_t reserved, const std::array& intentionalSignature) { // mfg filter logic is used to allow MTM keep alive command only in // manfacturing mode constexpr std::array signatureOk = {'I', 'N', 'T', 'E', 'L'}; if (intentionalSignature != signatureOk || reserved != 0) { return ipmi::responseInvalidFieldRequest(); } return ipmi::response(resetMtmTimer(ctx)); } static constexpr unsigned int makeCmdKey(unsigned int netFn, unsigned int cmd) { return (netFn << 8) | cmd; } ipmi::Cc mfgFilterMessage(ipmi::message::Request::ptr request) { // Restricted commands, must be executed only in Manufacturing mode switch (makeCmdKey(request->ctx->netFn, request->ctx->cmd)) { // i2c master write read command needs additional checking case makeCmdKey(ipmi::netFnApp, ipmi::app::cmdMasterWriteRead): if (request->payload.size() > 4) { // Allow write data count > 1 only in Special mode if (mtm.getMfgMode() == SpecialMode::none) { return ipmi::ccInsufficientPrivilege; } } return ipmi::ccSuccess; case makeCmdKey(ipmi::netFnOemOne, ipmi::intel::general::cmdGetSmSignal): case makeCmdKey(ipmi::netFnOemOne, ipmi::intel::general::cmdSetSmSignal): case makeCmdKey(ipmi::netFnOemOne, ipmi::intel::general::cmdMtmKeepAlive): case makeCmdKey(ipmi::netFnOemOne, ipmi::intel::general::cmdSetManufacturingData): case makeCmdKey(ipmi::netFnOemOne, ipmi::intel::general::cmdGetManufacturingData): case makeCmdKey(ipmi::intel::netFnGeneral, ipmi::intel::general::cmdSetFITcLayout): case makeCmdKey(ipmi::netFnOemOne, ipmi::intel::general::cmdMTMBMCFeatureControl): case makeCmdKey(ipmi::netFnStorage, ipmi::storage::cmdWriteFruData): case makeCmdKey(ipmi::netFnOemTwo, ipmi::intel::platform::cmdClearCMOS): // Check for Special mode if (mtm.getMfgMode() == SpecialMode::none) { return ipmi::ccInvalidCommand; } return ipmi::ccSuccess; case makeCmdKey(ipmi::netFnStorage, ipmi::storage::cmdDeleteSelEntry): { return ipmi::ccInvalidCommand; } } return ipmi::ccSuccess; } static constexpr uint8_t maxEthSize = 6; static constexpr uint8_t maxSupportedEth = 3; static constexpr const char* factoryEthAddrBaseFileName = "/var/sofs/factory-settings/network/mac/eth"; ipmi::RspType<> setManufacturingData(ipmi::Context::ptr ctx, uint8_t dataType, std::array ethData) { // mfg filter logic will restrict this command executing only in mfg mode. if (dataType >= maxSupportedEth) { return ipmi::responseParmOutOfRange(); } constexpr uint8_t invalidData = 0; constexpr uint8_t validData = 1; constexpr uint8_t ethAddrStrSize = 19; // XX:XX:XX:XX:XX:XX + \n + null termination; std::vector buff(ethAddrStrSize); std::snprintf(reinterpret_cast(buff.data()), ethAddrStrSize, "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx\n", ethData.at(0), ethData.at(1), ethData.at(2), ethData.at(3), ethData.at(4), ethData.at(5)); std::ofstream oEthFile(factoryEthAddrBaseFileName + std::to_string(dataType), std::ofstream::out); if (!oEthFile.good()) { return ipmi::responseUnspecifiedError(); } oEthFile << reinterpret_cast(buff.data()); oEthFile.flush(); oEthFile.close(); resetMtmTimer(ctx); return ipmi::responseSuccess(); } ipmi::RspType> getManufacturingData(ipmi::Context::ptr ctx, uint8_t dataType) { // mfg filter logic will restrict this command executing only in mfg mode. if (dataType >= maxSupportedEth) { return ipmi::responseParmOutOfRange(); } std::array ethData{0}; constexpr uint8_t invalidData = 0; constexpr uint8_t validData = 1; std::ifstream iEthFile(factoryEthAddrBaseFileName + std::to_string(dataType), std::ifstream::in); if (!iEthFile.good()) { return ipmi::responseSuccess(invalidData, ethData); } std::string ethStr; iEthFile >> ethStr; uint8_t* data = ethData.data(); std::sscanf(ethStr.c_str(), "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx", data, (data + 1), (data + 2), (data + 3), (data + 4), (data + 5)); resetMtmTimer(ctx); return ipmi::responseSuccess(validData, ethData); } /** @brief implements slot master write read IPMI command which can be used for * low-level I2C/SMBus write, read or write-read access for PCIE slots * @param reserved - skip 6 bit * @param addressType - address type * @param bbSlotNum - baseboard slot number * @param riserSlotNum - riser slot number * @param reserved2 - skip 2 bit * @param slaveAddr - slave address * @param readCount - number of bytes to be read * @param writeData - data to be written * * @returns IPMI completion code plus response data */ ipmi::RspType> appSlotI2CMasterWriteRead(uint6_t reserved, uint2_t addressType, uint3_t bbSlotNum, uint3_t riserSlotNum, uint2_t resvered2, uint8_t slaveAddr, uint8_t readCount, std::vector writeData) { const size_t writeCount = writeData.size(); std::string i2cBus; if (addressType == slotAddressTypeBus) { std::string path = "/dev/i2c-mux/Riser_" + std::to_string(static_cast(bbSlotNum)) + "_Mux/Pcie_Slot_" + std::to_string(static_cast(riserSlotNum)); if (std::filesystem::exists(path) && std::filesystem::is_symlink(path)) { i2cBus = std::filesystem::read_symlink(path); } else { phosphor::logging::log( "Master write read command: Cannot get BusID"); return ipmi::responseInvalidFieldRequest(); } } else if (addressType == slotAddressTypeUniqueid) { i2cBus = "/dev/i2c-" + std::to_string(static_cast(bbSlotNum) | (static_cast(riserSlotNum) << 3)); } else { phosphor::logging::log( "Master write read command: invalid request"); return ipmi::responseInvalidFieldRequest(); } // Allow single byte write as it is offset byte to read the data, rest allow // only in Special mode. if (writeCount > 1) { if (mtm.getMfgMode() == SpecialMode::none) { return ipmi::responseInsufficientPrivilege(); } } if (readCount > slotI2CMaxReadSize) { phosphor::logging::log( "Master write read command: Read count exceeds limit"); return ipmi::responseParmOutOfRange(); } if (!readCount && !writeCount) { phosphor::logging::log( "Master write read command: Read & write count are 0"); return ipmi::responseInvalidFieldRequest(); } std::vector readBuf(readCount); ipmi::Cc retI2C = ipmi::i2cWriteRead(i2cBus, slaveAddr, writeData, readBuf); if (retI2C != ipmi::ccSuccess) { return ipmi::response(retI2C); } return ipmi::responseSuccess(readBuf); } ipmi::RspType<> clearCMOS() { // There is an i2c device on bus 4, the slave address is 0x38. Based on the // spec, writing 0x1 to address 0x61 on this device, will trigger the clear // CMOS action. constexpr uint8_t slaveAddr = 0x38; std::string i2cBus = "/dev/i2c-4"; std::vector writeData = {0x61, 0x1}; std::vector readBuf(0); ipmi::Cc retI2C = ipmi::i2cWriteRead(i2cBus, slaveAddr, writeData, readBuf); return ipmi::response(retI2C); } ipmi::RspType<> setFITcLayout(uint32_t layout) { static constexpr const char* factoryFITcLayout = "/var/sofs/factory-settings/layout/fitc"; std::filesystem::path fitcDir = std::filesystem::path(factoryFITcLayout).parent_path(); std::error_code ec; std::filesystem::create_directories(fitcDir, ec); if (ec) { return ipmi::responseUnspecifiedError(); } try { std::ofstream file(factoryFITcLayout); file << layout; file.flush(); file.close(); } catch (const std::exception& e) { return ipmi::responseUnspecifiedError(); } return ipmi::responseSuccess(); } static std::vector getMCTPServiceConfigPaths(ipmi::Context::ptr& ctx) { boost::system::error_code ec; auto configPaths = ctx->bus->yield_method_call>( ctx->yield, ec, "xyz.openbmc_project.ObjectMapper", "/xyz/openbmc_project/object_mapper", "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", "/xyz/openbmc_project/inventory/system/board", 2, std::array{ "xyz.openbmc_project.Configuration.MctpConfiguration"}); if (ec) { throw std::runtime_error( "Failed to query configuration sub tree objects"); } return configPaths; } static ipmi::RspType<> startOrStopService(ipmi::Context::ptr& ctx, const uint8_t enable, const std::string& serviceName) { constexpr bool runtimeOnly = false; constexpr bool force = false; boost::system::error_code ec; switch (enable) { case ipmi::SupportedFeatureActions::stop: ctx->bus->yield_method_call(ctx->yield, ec, systemDService, systemDObjPath, systemDMgrIntf, "StopUnit", serviceName, "replace"); break; case ipmi::SupportedFeatureActions::start: ctx->bus->yield_method_call(ctx->yield, ec, systemDService, systemDObjPath, systemDMgrIntf, "StartUnit", serviceName, "replace"); break; case ipmi::SupportedFeatureActions::disable: ctx->bus->yield_method_call( ctx->yield, ec, systemDService, systemDObjPath, systemDMgrIntf, "MaskUnitFiles", std::array{serviceName.c_str()}, runtimeOnly, force); ctx->bus->yield_method_call( ctx->yield, ec, systemDService, systemDObjPath, systemDMgrIntf, "DisableUnitFiles", std::array{serviceName.c_str()}, runtimeOnly); break; case ipmi::SupportedFeatureActions::enable: ctx->bus->yield_method_call( ctx->yield, ec, systemDService, systemDObjPath, systemDMgrIntf, "UnmaskUnitFiles", std::array{serviceName.c_str()}, runtimeOnly); ctx->bus->yield_method_call( ctx->yield, ec, systemDService, systemDObjPath, systemDMgrIntf, "EnableUnitFiles", std::array{serviceName.c_str()}, runtimeOnly, force); break; default: phosphor::logging::log( "ERROR: Invalid feature action selected", phosphor::logging::entry("ACTION=%d", enable)); return ipmi::responseInvalidFieldRequest(); } if (ec) { phosphor::logging::log( "ERROR: Service start or stop failed", phosphor::logging::entry("SERVICE=%s", serviceName.c_str())); return ipmi::responseUnspecifiedError(); } return ipmi::responseSuccess(); } static std::string getMCTPServiceName(const std::string& objectPath) { const auto serviceArgument = boost::algorithm::replace_all_copy( boost::algorithm::replace_first_copy( objectPath, "/xyz/openbmc_project/inventory/system/board/", ""), "/", "_2f"); std::string unitName = "xyz.openbmc_project.mctpd@" + serviceArgument + ".service"; return unitName; } static ipmi::RspType<> handleMCTPFeature(ipmi::Context::ptr& ctx, const uint8_t enable, const std::string& binding) { std::vector configPaths; try { configPaths = getMCTPServiceConfigPaths(ctx); } catch (const std::exception& e) { phosphor::logging::log(e.what()); return ipmi::responseUnspecifiedError(); } for (const auto& objectPath : configPaths) { auto const pos = objectPath.find_last_of('/'); if (binding == objectPath.substr(pos + 1)) { return startOrStopService(ctx, enable, getMCTPServiceName(objectPath)); } } return ipmi::responseSuccess(); } /** @brief implements MTM BMC Feature Control IPMI command which can be * used to enable or disable the supported BMC features. * @param yield - context object that represents the currently executing * coroutine * @param feature - feature enum to enable or disable * @param enable - enable or disable the feature * @param featureArg - custom arguments for that feature * @param reserved - reserved for future use * * @returns IPMI completion code */ ipmi::RspType<> mtmBMCFeatureControl(ipmi::Context::ptr ctx, const uint8_t feature, const uint8_t enable, const uint8_t featureArg, const uint16_t reserved) { if (reserved != 0) { return ipmi::responseInvalidFieldRequest(); } switch (feature) { case ipmi::SupportedFeatureControls::mctp: switch (featureArg) { case ipmi::SupportedMCTPBindings::mctpPCIe: return handleMCTPFeature(ctx, enable, "MCTP_PCIe"); case ipmi::SupportedMCTPBindings::mctpSMBusHSBP: return handleMCTPFeature(ctx, enable, "MCTP_SMBus_HSBP"); case ipmi::SupportedMCTPBindings::mctpSMBusPCIeSlot: return handleMCTPFeature(ctx, enable, "MCTP_SMBus_PCIe_slot"); default: return ipmi::responseInvalidFieldRequest(); } break; default: return ipmi::responseInvalidFieldRequest(); } return ipmi::responseSuccess(); } } // namespace ipmi void register_mtm_commands() __attribute__((constructor)); void register_mtm_commands() { // ipmi::registerHandler(ipmi::prioOemBase, ipmi::intel::netFnGeneral, ipmi::intel::general::cmdGetSmSignal, ipmi::Privilege::Admin, ipmi::appMTMGetSignal); ipmi::registerHandler(ipmi::prioOemBase, ipmi::intel::netFnGeneral, ipmi::intel::general::cmdSetSmSignal, ipmi::Privilege::Admin, ipmi::appMTMSetSignal); ipmi::registerHandler(ipmi::prioOemBase, ipmi::intel::netFnGeneral, ipmi::intel::general::cmdMtmKeepAlive, ipmi::Privilege::Admin, ipmi::mtmKeepAlive); ipmi::registerHandler(ipmi::prioOemBase, ipmi::intel::netFnGeneral, ipmi::intel::general::cmdSetManufacturingData, ipmi::Privilege::Admin, ipmi::setManufacturingData); ipmi::registerHandler(ipmi::prioOemBase, ipmi::intel::netFnGeneral, ipmi::intel::general::cmdGetManufacturingData, ipmi::Privilege::Admin, ipmi::getManufacturingData); ipmi::registerHandler(ipmi::prioOemBase, ipmi::intel::netFnGeneral, ipmi::intel::general::cmdSetFITcLayout, ipmi::Privilege::Admin, ipmi::setFITcLayout); ipmi::registerHandler(ipmi::prioOemBase, ipmi::intel::netFnGeneral, ipmi::intel::general::cmdMTMBMCFeatureControl, ipmi::Privilege::Admin, ipmi::mtmBMCFeatureControl); ipmi::registerHandler(ipmi::prioOemBase, ipmi::intel::netFnApp, ipmi::intel::general::cmdSlotI2CMasterWriteRead, ipmi::Privilege::Admin, ipmi::appSlotI2CMasterWriteRead); ipmi::registerHandler(ipmi::prioOemBase, ipmi::intel::netFnPlatform, ipmi::intel::platform::cmdClearCMOS, ipmi::Privilege::Admin, ipmi::clearCMOS); ipmi::registerFilter(ipmi::prioOemBase, [](ipmi::message::Request::ptr request) { return ipmi::mfgFilterMessage(request); }); }