blob: 53a35a1af4dfa52d9b141ee0f5103551567f492b [file] [log] [blame]
/*
* Copyright (c) 2018-2021 Ampere Computing LLC
*
* 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 "oemcommands.hpp"
#include <boost/container/flat_map.hpp>
#include <ipmid/api.hpp>
#include <ipmid/types.hpp>
#include <ipmid/utils.hpp>
#include <phosphor-logging/log.hpp>
#include <cstdlib>
using namespace phosphor::logging;
using BasicVariantType =
std::variant<std::vector<std::string>, std::string, int64_t, uint64_t,
double, int32_t, uint32_t, int16_t, uint16_t, uint8_t, bool>;
using FruObjectType = boost::container::flat_map<
sdbusplus::message::object_path,
boost::container::flat_map<
std::string,
boost::container::flat_map<std::string, BasicVariantType>>>;
constexpr static const char* fruDeviceServiceName =
"xyz.openbmc_project.FruDevice";
constexpr static const char* chassisTypeRackMount = "23";
static inline auto response(uint8_t cc)
{
return std::make_tuple(cc, std::nullopt);
}
static inline auto responseFailure()
{
return response(responseFail);
}
/** @brief get Baseboard FRU's address
* @param - busIdx, address of I2C device
* @returns - true if successfully, false if fail
*/
[[maybe_unused]] static bool getBaseBoardFRUAddr(uint8_t& busIdx, uint8_t& addr)
{
bool retVal = false;
sd_bus* bus = NULL;
FruObjectType fruObjects;
/*
* Read all managed objects of FRU device
*/
int ret = sd_bus_default_system(&bus);
if (ret < 0)
{
phosphor::logging::log<phosphor::logging::level::ERR>(
"Failed to connect to system bus");
sd_bus_unref(bus);
return false;
}
sdbusplus::bus::bus dbus(bus);
auto mapperCall = dbus.new_method_call(fruDeviceServiceName, "/",
"org.freedesktop.DBus.ObjectManager",
"GetManagedObjects");
try
{
auto mapperReply = dbus.call(mapperCall);
mapperReply.read(fruObjects);
}
catch (sdbusplus::exception_t& e)
{
log<level::ERR>("Fail to call GetManagedObjects method");
sd_bus_unref(bus);
return false;
}
/*
* Scan all FRU objects to find out baseboard FRU device.
* The basedboard FRU device is indecate by chassis type
* is Rack Mount - "23"
*/
for (const auto& fruObj : fruObjects)
{
auto fruDeviceInf = fruObj.second.find("xyz.openbmc_project.FruDevice");
if (fruDeviceInf != fruObj.second.end())
{
auto chassisProperty = fruDeviceInf->second.find("CHASSIS_TYPE");
if (chassisProperty != fruDeviceInf->second.end())
{
std::string chassisType =
std::get<std::string>(chassisProperty->second);
auto busProperty = fruDeviceInf->second.find("BUS");
auto addrProperty = fruDeviceInf->second.find("ADDRESS");
if ((0 == chassisType.compare(chassisTypeRackMount)) &&
(busProperty != fruDeviceInf->second.end()) &&
(addrProperty != fruDeviceInf->second.end()))
{
busIdx = (uint8_t)std::get<uint32_t>(busProperty->second);
addr = (uint8_t)std::get<uint32_t>(addrProperty->second);
retVal = true;
break;
}
}
}
}
sd_bus_unref(bus);
return retVal;
}
/** @brief get Raw FRU's data
* @param - busIdx, address of I2C device.
* - fruData: data have been read
* @returns - true if successfully, false if fail
*/
static bool getRawFruData(uint8_t busIdx, uint8_t addr,
std::vector<uint8_t>& fruData)
{
bool retVal = false;
sd_bus* bus = NULL;
int ret = sd_bus_default_system(&bus);
if (ret < 0)
{
phosphor::logging::log<phosphor::logging::level::ERR>(
"Failed to connect to system bus",
phosphor::logging::entry("ERRNO=0x%X", -ret));
}
else
{
sdbusplus::bus::bus dbus(bus);
auto MapperCall = dbus.new_method_call(
fruDeviceServiceName, "/xyz/openbmc_project/FruDevice",
"xyz.openbmc_project.FruDeviceManager", "GetRawFru");
MapperCall.append(busIdx, addr);
try
{
auto mapperReply = dbus.call(MapperCall);
mapperReply.read(fruData);
retVal = true;
}
catch (sdbusplus::exception_t& e)
{
log<level::ERR>("Fail to read Raw FRU data from system bus\n");
}
}
sd_bus_unref(bus);
return retVal;
}
/** @brief update MAC address information in FRU data
* @param - fruData: FRU data
* - macAddress: MAC address information
* @returns - true if successfully, false if fail
*/
static bool updateMACAddrInFRU(std::vector<uint8_t>& fruData,
std::vector<uint8_t> macAddress)
{
bool retVal = false;
uint32_t areaOffset = fruData[3] * 8; /* Board area start offset */
char macAddressStr[18];
uint32_t boardLeng = 0;
uint8_t checkSumVal = 0;
/*
* Update MAC address at first custom field of Board Information Area.
*/
if (areaOffset != 0)
{
/*
* The Board Manufacturer type/length byte is stored
* at byte 0x06 of Board area.
*/
uint32_t fieldOffset = areaOffset + 6;
/*
* Scan all 5 predefined fields of Board area to jump to
* first Custom field.
*/
for (uint32_t i = 0; i < 5; i++)
{
fieldOffset += (fruData[fieldOffset] & 0x3f) + 1;
}
/*
* Update the MAC address information when type/length is not
* EndOfField byte and the length of Custom field is 17.
*/
if ((fruData[fieldOffset] != 0xc1) &&
((uint8_t)17 == (fruData[fieldOffset] & (uint8_t)0x3f)))
{
sprintf(macAddressStr, "%02X:%02X:%02X:%02X:%02X:%02X",
macAddress[0], macAddress[1], macAddress[2], macAddress[3],
macAddress[4], macAddress[5]);
/*
* Update 17 bytes of MAC address information
*/
fieldOffset++;
for (uint32_t i = 0; i < 17; i++)
{
fruData[fieldOffset + i] = macAddressStr[i];
}
/*
* Re-caculate the checksum of Board Information Area.
*/
boardLeng = fruData[areaOffset + 1] * 8;
for (uint32_t i = 0; i < boardLeng - 1; i++)
{
checkSumVal += fruData[areaOffset + i];
}
checkSumVal = ~checkSumVal + 1;
fruData[areaOffset + boardLeng - 1] = checkSumVal;
retVal = true;
}
else
{
phosphor::logging::log<phosphor::logging::level::ERR>(
"FRU does not include MAC address information");
}
}
else
{
phosphor::logging::log<phosphor::logging::level::ERR>(
"FRU does not include Board Information Area");
}
return retVal;
}
/** @brief write FRU data to EEPROM
* @param - busIdx and address of I2C device.
* - fruData: FRU data
* @returns - true if successfully, false if fail
*/
static bool writeFruData(uint8_t busIdx, uint8_t addr,
std::vector<uint8_t>& fruData)
{
bool retVal = false;
sd_bus* bus = NULL;
int ret = sd_bus_default_system(&bus);
if (ret < 0)
{
phosphor::logging::log<phosphor::logging::level::ERR>(
"Failed to connect to system bus",
phosphor::logging::entry("ERRNO=0x%X", -ret));
}
else
{
sdbusplus::bus::bus dbus(bus);
auto MapperCall = dbus.new_method_call(
fruDeviceServiceName, "/xyz/openbmc_project/FruDevice",
"xyz.openbmc_project.FruDeviceManager", "WriteFru");
MapperCall.append(busIdx, addr, fruData);
try
{
auto mapperReply = dbus.call(MapperCall);
retVal = true;
}
catch (sdbusplus::exception_t& e)
{
log<level::ERR>("Fail to Write FRU data via system bus\n");
}
}
sd_bus_unref(bus);
return retVal;
}
/** @brief execute a command and get the output of the command
* @param[in] the command
* @returns output of the command
*/
std::string exec(const char* cmd)
{
char buffer[128];
std::string result = "";
/* Pipe stream from a command */
FILE* pipe = popen(cmd, "r");
if (!pipe)
throw std::runtime_error("popen() failed!");
try
{
/* Reads a line from the specified stream and stores it */
while (fgets(buffer, sizeof buffer, pipe) != NULL)
{
result += buffer;
}
}
catch (...)
{
pclose(pipe);
throw;
}
/* Close a stream that was opened by popen() */
pclose(pipe);
return result;
}
/** @brief implements sync RTC time to BMC commands
* @param - None
* @returns IPMI completion code.
*/
ipmi::RspType<> ipmiSyncRTCTimeToBMC()
{
std::string cmd;
std::string cmdOutput;
int ret;
try
{
/* Check the mode of NTP in the system, set the system time in case the
* NTP mode is disabled.
*/
cmd = "systemctl status systemd-timesyncd.service | grep inactive";
cmdOutput = exec(cmd.c_str());
if (cmdOutput.empty())
{
log<level::INFO>("Can not set system time while the mode is NTP");
return responseFailure();
}
else
{
/* Sync time from RTC to BMC using hwclock */
ret = system("hwclock --hctosys");
if (ret == -1)
{
log<level::INFO>("Can not set the system time");
return responseFailure();
}
}
}
catch (const std::exception& e)
{
log<level::ERR>(e.what());
return responseFailure();
}
return ipmi::responseSuccess();
}
/** @brief implements ipmi oem command edit MAC address
* @param - new macAddress
* @returns - Fail or Success.
*/
ipmi::RspType<uint8_t> ipmiDocmdSetMacAddress(std::vector<uint8_t> macAddress)
{
std::vector<uint8_t> fruData;
uint8_t busIdx = 0;
uint8_t addrss = 0;
if (macAddress.size() != 6)
{
log<level::ERR>("new MAC address is invalid");
return responseFailure();
}
#if defined(MAC_ADDRESS_FRU_BUS) && defined(MAC_ADDRESS_FRU_ADDR)
/* Set BUS and Address of FRU device that includes MAC address */
busIdx = MAC_ADDRESS_FRU_BUS;
addrss = MAC_ADDRESS_FRU_ADDR;
#else
/* Calculate BUS and Address of FRU device that includes MAC address */
if (!getBaseBoardFRUAddr(busIdx, addrss))
{
log<level::ERR>(
"Can not get the bus and address of baseboard FRU device");
return responseFailure();
}
#endif
if (!getRawFruData(busIdx, addrss, fruData))
{
log<level::ERR>("Can not get raw FRU data");
return responseFailure();
}
if (!updateMACAddrInFRU(fruData, macAddress))
{
log<level::ERR>("Can not update MAC address");
return responseFailure();
}
if (!writeFruData(busIdx, addrss, fruData))
{
log<level::ERR>("Can not Write FRU data");
return responseFailure();
}
return ipmi::responseSuccess(macAddress.size());
}
void registerOEMFunctions() __attribute__((constructor));
void registerOEMFunctions()
{
ipmi::registerHandler(ipmi::prioOemBase, ipmi::ampere::netFnAmpere,
ipmi::general::cmdSyncRtcTime, ipmi::Privilege::User,
ipmiSyncRTCTimeToBMC);
ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::ampere::netFnAmpere,
ipmi::general::cmdEditBmcMacAdr,
ipmi::Privilege::User, ipmiDocmdSetMacAddress);
}