blob: 38c44d99c21a28d40ab3cd43597fab8ebea3b3b4 [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 <boost/container/flat_map.hpp>
#include <filesystem>
#include <fstream>
#include <ipmid/api.hpp>
#include <manufacturingcommands.hpp>
#include <oemcommands.hpp>
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(boost::asio::yield_context yield)
{
auto sdbusp = getSdBus();
boost::system::error_code ec;
sdbusp->yield_method_call<>(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, 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 (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()
{
if (revertFanPWM)
{
revertFanPWM = false;
disablePidControlService(false);
}
for (const auto& ledProperty : ledPropertyList)
{
const std::string& ledName = ledProperty.getName();
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<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::SdBusError& 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::SdBusError& 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(boost::asio::yield_context yield, uint8_t signalTypeByte,
uint8_t instance, uint8_t actionByte)
{
if (mtm.getAccessLvl() < MtmLvl::mtmAvailable)
{
return ipmi::responseInvalidCommand();
}
SmSignalGet signalType = static_cast<SmSignalGet>(signalTypeByte);
SmActionGet action = static_cast<SmActionGet>(actionByte);
switch (signalType)
{
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(yield);
return ipmi::responseSuccess(sensorVal, std::nullopt);
}
break;
case SmSignalGet::smFanTachometerGet:
{
auto sdbusp = getSdBus();
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 = sdbusp->yield_method_call<objFlatMap>(
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(yield);
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(yield);
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(yield);
return ipmi::responseSuccess(
static_cast<uint8_t>(std::stoi(carrier)), std::nullopt);
}
break;
default:
return ipmi::responseInvalidFieldRequest();
break;
}
}
ipmi::RspType<> appMTMSetSignal(boost::asio::yield_context yield,
uint8_t signalTypeByte, uint8_t instance,
uint8_t actionByte,
std::optional<uint8_t> pwmSpeed)
{
if (mtm.getAccessLvl() < MtmLvl::mtmAvailable)
{
return ipmi::responseInvalidCommand();
}
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;
default:
{
return ipmi::responseInvalidFieldRequest();
}
}
if (retCode == ccSuccess)
{
resetMtmTimer(yield);
}
return ipmi::response(retCode);
}
ipmi::RspType<> mtmKeepAlive(boost::asio::yield_context yield, uint8_t reserved,
const std::array<char, 5>& intentionalSignature)
{
// Allow MTM keep alive command only in manfacturing mode.
if (mtm.getAccessLvl() != MtmLvl::mtmAvailable)
{
return ipmi::responseInvalidCommand();
}
constexpr std::array<char, 5> signatureOk = {'I', 'N', 'T', 'E', 'L'};
if (intentionalSignature != signatureOk || reserved != 0)
{
return ipmi::responseInvalidFieldRequest();
}
return ipmi::response(resetMtmTimer(yield));
}
ipmi::Cc mfgFilterMessage(ipmi::message::Request::ptr request)
{
// i2c master write read command needs additional checking
if ((request->ctx->netFn == ipmi::netFnApp) &&
(request->ctx->cmd == ipmi::app::cmdMasterWriteRead))
{
if (request->payload.size() > 4)
{
// Allow write data count > 1, only if it is in MFG mode
if (mtm.getAccessLvl() != MtmLvl::mtmAvailable)
{
return ipmi::ccInsufficientPrivilege;
}
}
}
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(boost::asio::yield_context yield,
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;
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 << fflush;
oEthFile.close();
resetMtmTimer(yield);
return ipmi::responseSuccess();
}
ipmi::RspType<uint8_t, std::array<uint8_t, maxEthSize>>
getManufacturingData(boost::asio::yield_context yield, 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())
{
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(yield);
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 MFG mode.
if (writeCount > 1)
{
if (mtm.getAccessLvl() < MtmLvl::mtmAvailable)
{
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);
}
} // 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::netFnApp,
ipmi::intel::general::cmdSlotI2CMasterWriteRead,
ipmi::Privilege::Admin,
ipmi::appSlotI2CMasterWriteRead);
ipmi::registerFilter(ipmi::prioOemBase,
[](ipmi::message::Request::ptr request) {
return ipmi::mfgFilterMessage(request);
});
}