/* // 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 #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 bbRiserMux = 0; static constexpr uint8_t leftRiserMux = 1; static constexpr uint8_t rightRiserMux = 2; static constexpr uint8_t pcieMux = 3; static constexpr uint8_t hsbpMux = 4; 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) { lg2::error("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 ledPath = ledPathPrefix + ledName; ipmi::Value presentState; if (false == ledProp->getLock()) { if (mtm.getProperty(ledService, 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 (const sdbusplus::exception_t& e) { return IPMI_CC_UNSPECIFIED_ERROR; } mtm.revertLedCallback = false; } else { std::string ledName = ledProp->getName(); 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_t& e) { lg2::info("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_t& e) { lg2::info("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_t& e) { lg2::info("ERROR: phosphor-pid-control service start or stop failed"); return -1; } return 0; } static bool findPwmName(ipmi::Context::ptr& ctx, uint8_t instance, std::string& pwmName) { boost::system::error_code ec{}; ObjectValueTree obj; // GetAll the objects under service FruDevice ec = getManagedObjects(ctx, "xyz.openbmc_project.EntityManager", "/xyz/openbmc_project/inventory", obj); if (ec) { lg2::error("GetMangagedObjects failed", "ERROR", ec.message().c_str()); return false; } for (const auto& [path, objData] : obj) { for (const auto& [intf, propMap] : objData) { // Currently, these are the three different fan types supported. if (intf == "xyz.openbmc_project.Configuration.AspeedFan" || intf == "xyz.openbmc_project.Configuration.I2CFan" || intf == "xyz.openbmc_project.Configuration.NuvotonFan") { std::string fanPath = "/Fan_"; fanPath += std::to_string(instance); std::string objPath = path.str; objPath = objPath.substr(objPath.find_last_of("/")); if (objPath != fanPath) { continue; } auto connector = objData.find(intf + std::string(".Connector")); if (connector == objData.end()) { return false; } auto findPwmName = connector->second.find("PwmName"); if (findPwmName != connector->second.end()) { auto fanPwmName = std::get_if(&findPwmName->second); if (!fanPwmName) { lg2::error("PwmName parse ERROR."); return false; } pwmName = *fanPwmName; return true; } auto findPwm = connector->second.find("Pwm"); if (findPwm == connector->second.end()) { return false; } auto fanPwm = std::get_if(&findPwm->second); if (!fanPwm) { return false; } pwmName = "Pwm_" + std::to_string(*fanPwm + 1); return true; } } } return false; } 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 pwmName, fullPath; if (!findPwmName(ctx, instance + 1, pwmName)) { // The default PWM name is Pwm_# pwmName = "Pwm_" + std::to_string(instance + 1); } fullPath = fanPwmPath + pwmName; 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) { lg2::error("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; } [[fallthrough]]; } 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: lg2::info("case SmActionGet::sample"); break; case SmActionGet::ignore: { lg2::info("case SmActionGet::ignore"); if (mtm.setProperty(buttonService, path, buttonIntf, "ButtonMasked", true) < 0) { return ipmi::responseUnspecifiedError(); } } break; case SmActionGet::revert: { lg2::info("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: { lg2::info("case SmActionSet::forceDeasserted"); retCode = ledStoreAndSet(signalType, std::string("Off")); if (retCode != ccSuccess) { return ipmi::response(retCode); } mtm.revertTimer.start(revertTimeOut); } break; case SmActionSet::forceAsserted: { lg2::info("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: { lg2::info("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 pwmName, fanPwmInstancePath; if (!findPwmName(ctx, instance + 1, pwmName)) { pwmName = "Pwm_" + std::to_string(instance + 1); } fanPwmInstancePath = fanPwmPath + pwmName; 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: { lg2::info("Performing Speaker SmActionSet", "ACTION", lg2::dec, static_cast(action)); switch (action) { case SmActionSet::forceAsserted: { char beepDevName[] = "/dev/input/event0"; if (mtm.mtmTestBeepFd != -1) { lg2::info("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) { lg2::error("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)) { lg2::error("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) { lg2::error("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 controller 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"; bool findFruDevice(ipmi::Context::ptr& ctx, uint64_t& macOffset, uint64_t& busNum, uint64_t& address) { boost::system::error_code ec{}; ObjectValueTree obj; // GetAll the objects under service FruDevice ec = getManagedObjects(ctx, "xyz.openbmc_project.EntityManager", "/xyz/openbmc_project/inventory", obj); if (ec) { lg2::error("GetManagedObjects failed", "ERROR", ec.message().c_str()); return false; } for (const auto& [path, fru] : obj) { for (const auto& [intf, propMap] : fru) { if (intf == "xyz.openbmc_project.Inventory.Item.Board.Motherboard") { auto findBus = propMap.find("FruBus"); auto findAddress = propMap.find("FruAddress"); auto findMacOffset = propMap.find("MacOffset"); if (findBus == propMap.end() || findAddress == propMap.end() || findMacOffset == propMap.end()) { continue; } auto fruBus = std::get_if(&findBus->second); auto fruAddress = std::get_if(&findAddress->second); auto macFruOffset = std::get_if(&findMacOffset->second); if (!fruBus || !fruAddress || !macFruOffset) { lg2::info("ERROR: MotherBoard FRU config data type " "invalid, not used"); return false; } busNum = *fruBus; address = *fruAddress; macOffset = *macFruOffset; return true; } } } return false; } static constexpr uint64_t fruEnd = 0xff; // write rolls over within current page, need to keep mac within a page static constexpr uint64_t fruPageSize = 0x8; // MAC record struct: HEADER, MAC DATA, CheckSum static constexpr uint64_t macRecordSize = maxEthSize + 2; static_assert(fruPageSize >= macRecordSize, "macRecordSize greater than eeprom page size"); static constexpr uint8_t macHeader = 0x40; // Calculate new checksum for fru info area static uint8_t calculateChecksum(std::vector::const_iterator iter, std::vector::const_iterator end) { constexpr int checksumMod = 256; uint8_t sum = std::accumulate(iter, end, static_cast(0)); return (checksumMod - sum) % checksumMod; } bool readMacFromFru(ipmi::Context::ptr ctx, uint8_t macIndex, std::array& ethData) { uint64_t macOffset = fruEnd; uint64_t fruBus = 0; uint64_t fruAddress = 0; if (findFruDevice(ctx, macOffset, fruBus, fruAddress)) { lg2::info("Found mac fru", "BUS", lg2::dec, static_cast(fruBus), "ADDRESS", lg2::dec, static_cast(fruAddress)); if (macOffset % fruPageSize) { macOffset = (macOffset / fruPageSize + 1) * fruPageSize; } macOffset += macIndex * fruPageSize; if ((macOffset + macRecordSize) > fruEnd) { lg2::error("ERROR: read fru mac failed, offset invalid"); return false; } std::vector writeData; writeData.push_back(static_cast(macOffset)); std::vector readBuf(macRecordSize); std::string i2cBus = "/dev/i2c-" + std::to_string(fruBus); ipmi::Cc retI2C = ipmi::i2cWriteRead(i2cBus, fruAddress, writeData, readBuf); if (retI2C == ipmi::ccSuccess) { uint8_t cs = calculateChecksum(readBuf.cbegin(), readBuf.cend()); if (cs == 0) { std::copy(++readBuf.begin(), --readBuf.end(), ethData.data()); return true; } } } return false; } ipmi::Cc writeMacToFru(ipmi::Context::ptr ctx, uint8_t macIndex, std::array& ethData) { uint64_t macOffset = fruEnd; uint64_t fruBus = 0; uint64_t fruAddress = 0; if (findFruDevice(ctx, macOffset, fruBus, fruAddress)) { lg2::info("Found mac fru", "BUS", lg2::dec, static_cast(fruBus), "ADDRESS", lg2::dec, static_cast(fruAddress)); if (macOffset % fruPageSize) { macOffset = (macOffset / fruPageSize + 1) * fruPageSize; } macOffset += macIndex * fruPageSize; if ((macOffset + macRecordSize) > fruEnd) { lg2::error("ERROR: write mac fru failed, offset invalid."); return ipmi::ccParmOutOfRange; } std::vector writeData; writeData.reserve(macRecordSize + 1); // include start location writeData.push_back(static_cast(macOffset)); writeData.push_back(macHeader); std::for_each(ethData.cbegin(), ethData.cend(), [&](uint8_t i) { writeData.push_back(i); }); uint8_t macCheckSum = calculateChecksum(++writeData.cbegin(), writeData.cend()); writeData.push_back(macCheckSum); std::string i2cBus = "/dev/i2c-" + std::to_string(fruBus); std::vector readBuf; ipmi::Cc ret = ipmi::i2cWriteRead(i2cBus, fruAddress, writeData, readBuf); // prepare for read to detect chip is write protected writeData.resize(1); readBuf.resize(maxEthSize + 1); // include macHeader switch (ret) { case ipmi::ccSuccess: // Wait for internal write cycle to complete // example: ATMEL 24c0x chip has Twr spec as 5ms // Ideally we want yield wait, but currently following code // crash with "thread not supported" // boost::asio::deadline_timer timer( // boost::asio::get_associated_executor(ctx->yield), // boost::posix_time::seconds(1)); // timer.async_wait(ctx->yield); // use usleep as temp WA usleep(5000); if (ipmi::i2cWriteRead(i2cBus, fruAddress, writeData, readBuf) == ipmi::ccSuccess) { if (std::equal(ethData.begin(), ethData.end(), ++readBuf.begin())) // skip macHeader { return ipmi::ccSuccess; } lg2::info("INFO: write mac fru verify failed, fru may be " "write protected."); } return ipmi::ccCommandNotAvailable; default: if (ipmi::i2cWriteRead(i2cBus, fruAddress, writeData, readBuf) == ipmi::ccSuccess) { lg2::info("INFO: write mac fru failed, but successfully " "read from fru, fru may be write protected."); return ipmi::ccCommandNotAvailable; } else // assume failure is due to no eeprom on board { lg2::error("ERROR: write mac fru failed, assume no eeprom " "is available."); } break; } } // no FRU eeprom found return ipmi::ccDestinationUnavailable; } 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(); } ipmi::Cc ret = writeMacToFru(ctx, dataType, ethData); if (ret != ipmi::ccDestinationUnavailable) { resetMtmTimer(ctx); return response(ret); } 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()) { if (readMacFromFru(ctx, dataType, ethData)) { resetMtmTimer(ctx); return ipmi::responseSuccess(validData, ethData); } 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 controller 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 3 bit * @param muxType - mux type * @param addressType - address type * @param bbSlotNum - baseboard slot number * @param riserSlotNum - riser slot number * @param reserved2 - skip 2 bit * @param targetAddr - target address * @param readCount - number of bytes to be read * @param writeData - data to be written * * @returns IPMI completion code plus response data */ ipmi::RspType> appSlotI2CControllerWriteRead( uint3_t reserved, uint3_t muxType, uint2_t addressType, uint3_t bbSlotNum, uint3_t riserSlotNum, uint2_t reserved2, uint8_t targetAddr, uint8_t readCount, std::vector writeData) { if (reserved || reserved2) { return ipmi::responseInvalidFieldRequest(); } const size_t writeCount = writeData.size(); std::string i2cBus; if (addressType == slotAddressTypeBus) { std::string path = "/dev/i2c-mux/"; if (muxType == bbRiserMux) { path += "Riser_" + std::to_string(static_cast(bbSlotNum)) + "_Mux/Pcie_Slot_" + std::to_string(static_cast(riserSlotNum)); } else if (muxType == leftRiserMux) { path += "Left_Riser_Mux/Slot_" + std::to_string(static_cast(riserSlotNum)); } else if (muxType == rightRiserMux) { path += "Right_Riser_Mux/Slot_" + std::to_string(static_cast(riserSlotNum)); } else if (muxType == pcieMux) { path += "PCIe_Mux/Slot_" + std::to_string(static_cast(riserSlotNum)); } else if (muxType == hsbpMux) { path += "HSBP_Mux/Slot" + std::to_string(static_cast(riserSlotNum)); } phosphor::logging::log( ("Path is: " + path).c_str()); if (std::filesystem::exists(path) && std::filesystem::is_symlink(path)) { i2cBus = std::filesystem::read_symlink(path); } else { lg2::error("Controller 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 { lg2::error("Controller 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) { lg2::error("Controller write read command: Read count exceeds limit"); return ipmi::responseParmOutOfRange(); } if (!readCount && !writeCount) { lg2::error("Controller write read command: Read & write count are 0"); return ipmi::responseInvalidFieldRequest(); } std::vector readBuf(readCount); ipmi::Cc retI2C = ipmi::i2cWriteRead(i2cBus, targetAddr, 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 target address is 0x38. Based on // the spec, writing 0x1 to address 0x61 on this device, will trigger // the clear CMOS action. constexpr uint8_t targetAddr = 0x38; std::string i2cBus = "/dev/i2c-4"; std::vector writeData = {0x61, 0x1}; std::vector readBuf(0); ipmi::Cc retI2C = ipmi::i2cWriteRead(i2cBus, targetAddr, 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, bool disableOrEnableUnitFiles = true) { 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: if (disableOrEnableUnitFiles == true) { ctx->bus->yield_method_call( ctx->yield, ec, systemDService, systemDObjPath, systemDMgrIntf, "DisableUnitFiles", std::array{serviceName.c_str()}, runtimeOnly); } ctx->bus->yield_method_call( ctx->yield, ec, systemDService, systemDObjPath, systemDMgrIntf, "MaskUnitFiles", std::array{serviceName.c_str()}, runtimeOnly, force); break; case ipmi::SupportedFeatureActions::enable: ctx->bus->yield_method_call( ctx->yield, ec, systemDService, systemDObjPath, systemDMgrIntf, "UnmaskUnitFiles", std::array{serviceName.c_str()}, runtimeOnly); if (disableOrEnableUnitFiles == true) { ctx->bus->yield_method_call( ctx->yield, ec, systemDService, systemDObjPath, systemDMgrIntf, "EnableUnitFiles", std::array{serviceName.c_str()}, runtimeOnly, force); } break; default: lg2::warning("ERROR: Invalid feature action selected", "ACTION", lg2::dec, enable); return ipmi::responseInvalidFieldRequest(); } if (ec) { lg2::warning("ERROR: Service start or stop failed", "SERVICE", 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) { lg2::error(e.what()); return ipmi::responseUnspecifiedError(); } for (const auto& objectPath : configPaths) { const auto pos = objectPath.find_last_of('/'); if (binding == objectPath.substr(pos + 1)) { return startOrStopService(ctx, enable, getMCTPServiceName(objectPath), false); } } return ipmi::responseSuccess(); } static bool isNum(const std::string& s) { if (s.empty()) { return false; } uint8_t busNumber; const auto sEnd = s.data() + s.size(); const auto& [ptr, ec] = std::from_chars(s.data(), sEnd, busNumber); if (ec == std::errc() || ptr == sEnd) { return true; } return false; } bool getBusNumFromPath(const std::string& path, std::string& busStr) { std::vector parts; boost::split(parts, path, boost::is_any_of("-")); if (parts.size() == 2) { busStr = parts[1]; if (isNum(busStr)) { return true; } } return false; } static ipmi::RspType<> muxSlotDisable(ipmi::Context::ptr& ctx, std::string service, std::string muxName, uint8_t action, uint8_t slotNum) { boost::system::error_code ec; const std::filesystem::path muxSymlinkDirPath = "/dev/i2c-mux/" + muxName + "/Slot_" + std::to_string(slotNum + 1); if (!std::filesystem::is_symlink(muxSymlinkDirPath)) { return ipmi::responseInvalidFieldRequest(); } std::string linkPath = std::filesystem::read_symlink(muxSymlinkDirPath); std::string portNum; if (!getBusNumFromPath(linkPath, portNum)) { return ipmi::responseInvalidFieldRequest(); } auto res = ctx->bus->yield_method_call( ctx->yield, ec, service, mctpObjPath, mctpBaseIntf, "SkipList", std::vector{action, static_cast(std::stoi(portNum))}); if (ec) { lg2::error("Failed to set mctp skiplist"); return ipmi::responseUnspecifiedError(); } if (!res) { return ipmi::responseResponseError(); } return ipmi::responseSuccess(); } static ipmi::RspType<> handleMCTPSlotFeature( ipmi::Context::ptr& ctx, const uint8_t enable, const uint8_t featureArg) { uint8_t slotNum = (featureArg & slotNumMask); switch ((featureArg & muxTypeMask) >> muxTypeShift) { case ipmi::SupportedFeatureMuxs::pcieMuxSlot: return muxSlotDisable(ctx, mctpPcieSlotService, "PCIe_Mux", enable, slotNum); break; case ipmi::SupportedFeatureMuxs::pcieMcioMuxSlot: return muxSlotDisable(ctx, mctpPcieSlotService, "PCIe_MCIO_Mux", enable, slotNum); break; case ipmi::SupportedFeatureMuxs::pcieM2EdSffMuxSlot: return muxSlotDisable(ctx, mctpPcieSlotService, "M2_EDSFF_Mux", enable, slotNum); break; case ipmi::SupportedFeatureMuxs::leftRaiserMuxSlot: return muxSlotDisable(ctx, mctpPcieSlotService, "Left_Riser_Mux", enable, slotNum); break; case ipmi::SupportedFeatureMuxs::rightRaiserMuxSlot: return muxSlotDisable(ctx, mctpPcieSlotService, "Right_Riser_Mux", enable, slotNum); break; case ipmi::SupportedFeatureMuxs::HsbpMuxSlot: return muxSlotDisable(ctx, mctpHsbpService, "HSBP_Mux", enable, slotNum); break; default: lg2::warning("ERROR: Invalid Mux slot selected"); return ipmi::responseInvalidFieldRequest(); } } /** @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; case ipmi::SupportedFeatureControls::pcieScan: if (featureArg != 0) { return ipmi::responseInvalidFieldRequest(); } startOrStopService(ctx, enable, "xyz.openbmc_project.PCIe.service"); break; case ipmi::SupportedFeatureControls::mctpSlotSupport: return handleMCTPSlotFeature(ctx, enable, featureArg); 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::cmdSlotI2CControllerWriteRead, ipmi::Privilege::Admin, ipmi::appSlotI2CControllerWriteRead); 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); }); }