blob: 6aed0e99eebeff6e5c4acadebaa0ef4c8437f2af [file] [log] [blame]
/*
// 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 <linux/input.h>
#include <boost/algorithm/string.hpp>
#include <boost/container/flat_map.hpp>
#include <ipmid/api.hpp>
#include <manufacturingcommands.hpp>
#include <oemcommands.hpp>
#include <filesystem>
#include <fstream>
namespace ipmi
{
Manufacturing mtm;
static auto revertTimeOut =
std::chrono::duration_cast<std::chrono::microseconds>(
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<phosphor::logging::level::ERR>(
"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<std::string>(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 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_t& e)
{
phosphor::logging::log<phosphor::logging::level::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)
{
phosphor::logging::log<phosphor::logging::level::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)
{
phosphor::logging::log<phosphor::logging::level::INFO>(
"ERROR: phosphor-pid-control service start or stop failed");
return -1;
}
return 0;
}
ipmi::RspType<uint8_t, // Signal value
std::optional<uint16_t> // 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<SmSignalGet>(signalTypeByte);
SmActionGet action = static_cast<SmActionGet>(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<std::string>(&reply);
if (!intrusionStatus)
{
return ipmi::responseUnspecifiedError();
}
uint8_t status = 0;
if (!intrusionStatus->compare("Normal"))
{
status = static_cast<uint8_t>(IntrusionStatus::normal);
}
else if (!intrusionStatus->compare("HardwareIntrusion"))
{
status =
static_cast<uint8_t>(IntrusionStatus::hardwareIntrusion);
}
else if (!intrusionStatus->compare("TamperingDetected"))
{
status =
static_cast<uint8_t>(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<double>(&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<std::string>>>;
auto flatMap = ctx->bus->yield_method_call<objFlatMap>(
ctx->yield, ec, "xyz.openbmc_project.ObjectMapper",
"/xyz/openbmc_project/object_mapper",
"xyz.openbmc_project.ObjectMapper", "GetSubTree",
fanTachBasePath, 0, std::array<const char*, 1>{fanIntf});
if (ec)
{
phosphor::logging::log<phosphor::logging::level::ERR>(
"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<double>(&reply);
if (doubleVal == nullptr)
{
return ipmi::responseUnspecifiedError();
}
uint8_t sensorVal = FAN_PRESENT | FAN_SENSOR_PRESENT;
std::optional<uint16_t> 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<phosphor::logging::level::INFO>(
"case SmActionGet::sample");
break;
case SmActionGet::ignore:
{
phosphor::logging::log<phosphor::logging::level::INFO>(
"case SmActionGet::ignore");
if (mtm.setProperty(buttonService, path, buttonIntf,
"ButtonMasked", true) < 0)
{
return ipmi::responseUnspecifiedError();
}
}
break;
case SmActionGet::revert:
{
phosphor::logging::log<phosphor::logging::level::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<bool>(&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<uint8_t>(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<uint8_t> pwmSpeed)
{
// mfg filter logic is used to allow MTM set signal command only in
// manfacturing mode.
SmSignalSet signalType = static_cast<SmSignalSet>(signalTypeByte);
SmActionSet action = static_cast<SmActionSet>(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<phosphor::logging::level::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:
{
phosphor::logging::log<phosphor::logging::level::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:
{
phosphor::logging::log<phosphor::logging::level::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 fanPwmInstancePath =
fanPwmPath + std::to_string(instance + 1);
ret =
mtm.setProperty(fanService, fanPwmInstancePath, fanIntf,
"Value", static_cast<double>(pwmValue));
if (ret < 0)
{
return ipmi::responseUnspecifiedError();
}
}
break;
default:
{
return ipmi::responseInvalidFieldRequest();
}
}
}
break;
case SmSignalSet::smSpeaker:
{
phosphor::logging::log<phosphor::logging::level::INFO>(
"Performing Speaker SmActionSet",
phosphor::logging::entry("ACTION=%d",
static_cast<uint8_t>(action)));
switch (action)
{
case SmActionSet::forceAsserted:
{
char beepDevName[] = "/dev/input/event0";
if (mtm.mtmTestBeepFd != -1)
{
phosphor::logging::log<phosphor::logging::level::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)
{
phosphor::logging::log<phosphor::logging::level::ERR>(
"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<phosphor::logging::level::ERR>(
"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>;
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<objPaths>(
ctx->yield, ec, "xyz.openbmc_project.ObjectMapper",
"/xyz/openbmc_project/object_mapper",
"xyz.openbmc_project.ObjectMapper", "GetSubTreePaths",
driveBasePath, 0, std::array<const char*, 1>{driveLedIntf});
if (ec)
{
phosphor::logging::log<phosphor::logging::level::ERR>(
"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<char, 5>& intentionalSignature)
{
// mfg filter logic is used to allow MTM keep alive command only in
// manfacturing mode
constexpr std::array<char, 5> 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";
using ObjectType = boost::container::flat_map<
std::string, boost::container::flat_map<std::string, DbusVariant>>;
using ManagedObjectType =
boost::container::flat_map<sdbusplus::message::object_path, ObjectType>;
bool findFruDevice(const std::shared_ptr<sdbusplus::asio::connection>& bus,
boost::asio::yield_context& yield, uint64_t& macOffset,
uint64_t& busNum, uint64_t& address)
{
boost::system::error_code ec;
// GetAll the objects under service FruDevice
ec = boost::system::errc::make_error_code(boost::system::errc::success);
auto obj = bus->yield_method_call<ManagedObjectType>(
yield, ec, "xyz.openbmc_project.EntityManager", "/",
"org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
if (ec)
{
phosphor::logging::log<phosphor::logging::level::ERR>(
"GetMangagedObjects failed",
phosphor::logging::entry("ERROR=%s", 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<uint64_t>(&findBus->second);
auto fruAddress = std::get_if<uint64_t>(&findAddress->second);
auto macFruOffset =
std::get_if<uint64_t>(&findMacOffset->second);
if (!fruBus || !fruAddress || !macFruOffset)
{
phosphor::logging::log<phosphor::logging::level::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<uint8_t>::const_iterator iter,
std::vector<uint8_t>::const_iterator end)
{
constexpr int checksumMod = 256;
uint8_t sum = std::accumulate(iter, end, static_cast<uint8_t>(0));
return (checksumMod - sum) % checksumMod;
}
bool readMacFromFru(ipmi::Context::ptr ctx, uint8_t macIndex,
std::array<uint8_t, maxEthSize>& ethData)
{
uint64_t macOffset = fruEnd;
uint64_t fruBus = 0;
uint64_t fruAddress = 0;
if (findFruDevice(ctx->bus, ctx->yield, macOffset, fruBus, fruAddress))
{
phosphor::logging::log<phosphor::logging::level::INFO>(
"Found mac fru",
phosphor::logging::entry("BUS=%d", static_cast<uint8_t>(fruBus)),
phosphor::logging::entry("ADDRESS=%d",
static_cast<uint8_t>(fruAddress)));
if (macOffset % fruPageSize)
{
macOffset = (macOffset / fruPageSize + 1) * fruPageSize;
}
macOffset += macIndex * fruPageSize;
if ((macOffset + macRecordSize) > fruEnd)
{
phosphor::logging::log<phosphor::logging::level::ERR>(
"ERROR: read fru mac failed, offset invalid");
return false;
}
std::vector<uint8_t> writeData;
writeData.push_back(static_cast<uint8_t>(macOffset));
std::vector<uint8_t> 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<uint8_t, maxEthSize>& ethData)
{
uint64_t macOffset = fruEnd;
uint64_t fruBus = 0;
uint64_t fruAddress = 0;
if (findFruDevice(ctx->bus, ctx->yield, macOffset, fruBus, fruAddress))
{
phosphor::logging::log<phosphor::logging::level::INFO>(
"Found mac fru",
phosphor::logging::entry("BUS=%d", static_cast<uint8_t>(fruBus)),
phosphor::logging::entry("ADDRESS=%d",
static_cast<uint8_t>(fruAddress)));
if (macOffset % fruPageSize)
{
macOffset = (macOffset / fruPageSize + 1) * fruPageSize;
}
macOffset += macIndex * fruPageSize;
if ((macOffset + macRecordSize) > fruEnd)
{
phosphor::logging::log<phosphor::logging::level::ERR>(
"ERROR: write mac fru failed, offset invalid.");
return ipmi::ccParmOutOfRange;
}
std::vector<uint8_t> writeData;
writeData.reserve(macRecordSize + 1); // include start location
writeData.push_back(static_cast<uint8_t>(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<uint8_t> 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;
}
phosphor::logging::log<phosphor::logging::level::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)
{
phosphor::logging::log<phosphor::logging::level::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
{
phosphor::logging::log<phosphor::logging::level::ERR>(
"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<uint8_t, maxEthSize> 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;
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<uint8_t> buff(ethAddrStrSize);
std::snprintf(reinterpret_cast<char*>(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<char*>(buff.data());
oEthFile.flush();
oEthFile.close();
resetMtmTimer(ctx);
return ipmi::responseSuccess();
}
ipmi::RspType<uint8_t, std::array<uint8_t, maxEthSize>>
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<uint8_t, maxEthSize> 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 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<std::vector<uint8_t>>
appSlotI2CMasterWriteRead(uint6_t reserved, uint2_t addressType,
uint3_t bbSlotNum, uint3_t riserSlotNum,
uint2_t resvered2, uint8_t slaveAddr,
uint8_t readCount, std::vector<uint8_t> 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<uint8_t>(bbSlotNum)) +
"_Mux/Pcie_Slot_" +
std::to_string(static_cast<uint8_t>(riserSlotNum));
if (std::filesystem::exists(path) && std::filesystem::is_symlink(path))
{
i2cBus = std::filesystem::read_symlink(path);
}
else
{
phosphor::logging::log<phosphor::logging::level::ERR>(
"Master write read command: Cannot get BusID");
return ipmi::responseInvalidFieldRequest();
}
}
else if (addressType == slotAddressTypeUniqueid)
{
i2cBus = "/dev/i2c-" +
std::to_string(static_cast<uint8_t>(bbSlotNum) |
(static_cast<uint8_t>(riserSlotNum) << 3));
}
else
{
phosphor::logging::log<phosphor::logging::level::ERR>(
"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<phosphor::logging::level::ERR>(
"Master write read command: Read count exceeds limit");
return ipmi::responseParmOutOfRange();
}
if (!readCount && !writeCount)
{
phosphor::logging::log<phosphor::logging::level::ERR>(
"Master write read command: Read & write count are 0");
return ipmi::responseInvalidFieldRequest();
}
std::vector<uint8_t> 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<uint8_t> writeData = {0x61, 0x1};
std::vector<uint8_t> 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<std::string>
getMCTPServiceConfigPaths(ipmi::Context::ptr& ctx)
{
boost::system::error_code ec;
auto configPaths = ctx->bus->yield_method_call<std::vector<std::string>>(
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<const char*, 1>{
"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<const char*, 1>{serviceName.c_str()},
runtimeOnly);
}
ctx->bus->yield_method_call(
ctx->yield, ec, systemDService, systemDObjPath, systemDMgrIntf,
"MaskUnitFiles",
std::array<const char*, 1>{serviceName.c_str()}, runtimeOnly,
force);
break;
case ipmi::SupportedFeatureActions::enable:
ctx->bus->yield_method_call(
ctx->yield, ec, systemDService, systemDObjPath, systemDMgrIntf,
"UnmaskUnitFiles",
std::array<const char*, 1>{serviceName.c_str()}, runtimeOnly);
if (disableOrEnableUnitFiles == true)
{
ctx->bus->yield_method_call(
ctx->yield, ec, systemDService, systemDObjPath,
systemDMgrIntf, "EnableUnitFiles",
std::array<const char*, 1>{serviceName.c_str()},
runtimeOnly, force);
}
break;
default:
phosphor::logging::log<phosphor::logging::level::WARNING>(
"ERROR: Invalid feature action selected",
phosphor::logging::entry("ACTION=%d", enable));
return ipmi::responseInvalidFieldRequest();
}
if (ec)
{
phosphor::logging::log<phosphor::logging::level::WARNING>(
"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<std::string> configPaths;
try
{
configPaths = getMCTPServiceConfigPaths(ctx);
}
catch (const std::exception& e)
{
phosphor::logging::log<phosphor::logging::level::ERR>(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), false);
}
}
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;
case ipmi::SupportedFeatureControls::pcieScan:
if (featureArg != 0)
{
return ipmi::responseInvalidFieldRequest();
}
startOrStopService(ctx, enable, "xyz.openbmc_project.PCIe.service");
break;
default:
return ipmi::responseInvalidFieldRequest();
}
return ipmi::responseSuccess();
}
} // namespace ipmi
void register_mtm_commands() __attribute__((constructor));
void register_mtm_commands()
{
// <Get SM Signal>
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);
});
}