blob: 836537c1658650d8b4edac160b8f4359224f8140 [file] [log] [blame]
/*
// Copyright (c) 2017 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 <algorithm>
#include <array>
#include <boost/algorithm/string.hpp>
#include <boost/container/flat_map.hpp>
#include <chrono>
#include <cmath>
#include <commandutils.hpp>
#include <cstring>
#include <iostream>
#include <ipmi_to_redfish_hooks.hpp>
#include <ipmid/api.hpp>
#include <ipmid/utils.hpp>
#include <map>
#include <memory>
#include <optional>
#include <phosphor-logging/log.hpp>
#include <sdbusplus/bus.hpp>
#include <sdrutils.hpp>
#include <sensorcommands.hpp>
#include <sensorutils.hpp>
#include <stdexcept>
#include <storagecommands.hpp>
#include <string>
#include <utility>
#include <variant>
namespace ipmi
{
using ManagedObjectType =
std::map<sdbusplus::message::object_path,
std::map<std::string, std::map<std::string, DbusVariant>>>;
using SensorMap = std::map<std::string, std::map<std::string, DbusVariant>>;
static constexpr int sensorListUpdatePeriod = 10;
static constexpr int sensorMapUpdatePeriod = 2;
constexpr size_t maxSDRTotalSize =
76; // Largest SDR Record Size (type 01) + SDR Overheader Size
constexpr static const uint32_t noTimestamp = 0xFFFFFFFF;
static uint16_t sdrReservationID;
static uint32_t sdrLastAdd = noTimestamp;
static uint32_t sdrLastRemove = noTimestamp;
SensorSubTree sensorTree;
static boost::container::flat_map<std::string, ManagedObjectType> SensorCache;
// Specify the comparison required to sort and find char* map objects
struct CmpStr
{
bool operator()(const char *a, const char *b) const
{
return std::strcmp(a, b) < 0;
}
};
const static boost::container::flat_map<const char *, SensorUnits, CmpStr>
sensorUnits{{{"temperature", SensorUnits::degreesC},
{"voltage", SensorUnits::volts},
{"current", SensorUnits::amps},
{"fan_tach", SensorUnits::rpm},
{"power", SensorUnits::watts}}};
void registerSensorFunctions() __attribute__((constructor));
static sdbusplus::bus::match::match sensorAdded(
*getSdBus(),
"type='signal',member='InterfacesAdded',arg0path='/xyz/openbmc_project/"
"sensors/'",
[](sdbusplus::message::message &m) {
sensorTree.clear();
sdrLastAdd = std::chrono::duration_cast<std::chrono::seconds>(
std::chrono::system_clock::now().time_since_epoch())
.count();
});
static sdbusplus::bus::match::match sensorRemoved(
*getSdBus(),
"type='signal',member='InterfacesRemoved',arg0path='/xyz/openbmc_project/"
"sensors/'",
[](sdbusplus::message::message &m) {
sensorTree.clear();
sdrLastRemove = std::chrono::duration_cast<std::chrono::seconds>(
std::chrono::system_clock::now().time_since_epoch())
.count();
});
// this keeps track of deassertions for sensor event status command. A
// deasertion can only happen if an assertion was seen first.
static boost::container::flat_map<
std::string, boost::container::flat_map<std::string, std::optional<bool>>>
thresholdDeassertMap;
static sdbusplus::bus::match::match thresholdChanged(
*getSdBus(),
"type='signal',member='PropertiesChanged',interface='org.freedesktop.DBus."
"Properties',arg0namespace='xyz.openbmc_project.Sensor.Threshold'",
[](sdbusplus::message::message &m) {
boost::container::flat_map<std::string, std::variant<bool, double>>
values;
m.read(std::string(), values);
auto findAssert =
std::find_if(values.begin(), values.end(), [](const auto &pair) {
return pair.first.find("Alarm") != std::string::npos;
});
if (findAssert != values.end())
{
auto ptr = std::get_if<bool>(&(findAssert->second));
if (ptr == nullptr)
{
phosphor::logging::log<phosphor::logging::level::ERR>(
"thresholdChanged: Assert non bool");
return;
}
if (*ptr)
{
phosphor::logging::log<phosphor::logging::level::INFO>(
"thresholdChanged: Assert",
phosphor::logging::entry("SENSOR=%s", m.get_path()));
thresholdDeassertMap[m.get_path()][findAssert->first] = *ptr;
}
else
{
auto &value =
thresholdDeassertMap[m.get_path()][findAssert->first];
if (value)
{
phosphor::logging::log<phosphor::logging::level::INFO>(
"thresholdChanged: deassert",
phosphor::logging::entry("SENSOR=%s", m.get_path()));
value = *ptr;
}
}
}
});
static void getSensorMaxMin(const SensorMap &sensorMap, double &max,
double &min)
{
max = 127;
min = -128;
auto sensorObject = sensorMap.find("xyz.openbmc_project.Sensor.Value");
auto critical =
sensorMap.find("xyz.openbmc_project.Sensor.Threshold.Critical");
auto warning =
sensorMap.find("xyz.openbmc_project.Sensor.Threshold.Warning");
if (sensorObject != sensorMap.end())
{
auto maxMap = sensorObject->second.find("MaxValue");
auto minMap = sensorObject->second.find("MinValue");
if (maxMap != sensorObject->second.end())
{
max = std::visit(VariantToDoubleVisitor(), maxMap->second);
}
if (minMap != sensorObject->second.end())
{
min = std::visit(VariantToDoubleVisitor(), minMap->second);
}
}
if (critical != sensorMap.end())
{
auto lower = critical->second.find("CriticalLow");
auto upper = critical->second.find("CriticalHigh");
if (lower != critical->second.end())
{
double value = std::visit(VariantToDoubleVisitor(), lower->second);
min = std::min(value, min);
}
if (upper != critical->second.end())
{
double value = std::visit(VariantToDoubleVisitor(), upper->second);
max = std::max(value, max);
}
}
if (warning != sensorMap.end())
{
auto lower = warning->second.find("WarningLow");
auto upper = warning->second.find("WarningHigh");
if (lower != warning->second.end())
{
double value = std::visit(VariantToDoubleVisitor(), lower->second);
min = std::min(value, min);
}
if (upper != warning->second.end())
{
double value = std::visit(VariantToDoubleVisitor(), upper->second);
max = std::max(value, max);
}
}
}
static bool getSensorMap(std::string sensorConnection, std::string sensorPath,
SensorMap &sensorMap)
{
static boost::container::flat_map<
std::string, std::chrono::time_point<std::chrono::steady_clock>>
updateTimeMap;
auto updateFind = updateTimeMap.find(sensorConnection);
auto lastUpdate = std::chrono::time_point<std::chrono::steady_clock>();
if (updateFind != updateTimeMap.end())
{
lastUpdate = updateFind->second;
}
auto now = std::chrono::steady_clock::now();
if (std::chrono::duration_cast<std::chrono::seconds>(now - lastUpdate)
.count() > sensorMapUpdatePeriod)
{
updateTimeMap[sensorConnection] = now;
std::shared_ptr<sdbusplus::asio::connection> dbus = getSdBus();
auto managedObj = dbus->new_method_call(
sensorConnection.c_str(), "/", "org.freedesktop.DBus.ObjectManager",
"GetManagedObjects");
ManagedObjectType managedObjects;
try
{
auto reply = dbus->call(managedObj);
reply.read(managedObjects);
}
catch (sdbusplus::exception_t &)
{
phosphor::logging::log<phosphor::logging::level::ERR>(
"Error getting managed objects from connection",
phosphor::logging::entry("CONNECTION=%s",
sensorConnection.c_str()));
return false;
}
SensorCache[sensorConnection] = managedObjects;
}
auto connection = SensorCache.find(sensorConnection);
if (connection == SensorCache.end())
{
return false;
}
auto path = connection->second.find(sensorPath);
if (path == connection->second.end())
{
return false;
}
sensorMap = path->second;
return true;
}
/* sensor commands */
ipmi_ret_t ipmiSensorWildcardHandler(ipmi_netfn_t netfn, ipmi_cmd_t cmd,
ipmi_request_t request,
ipmi_response_t response,
ipmi_data_len_t dataLen,
ipmi_context_t context)
{
*dataLen = 0;
printCommand(+netfn, +cmd);
return IPMI_CC_INVALID;
}
namespace meHealth
{
constexpr const char *busname = "xyz.openbmc_project.NodeManagerProxy";
constexpr const char *path = "/xyz/openbmc_project/status/me";
constexpr const char *interface = "xyz.openbmc_project.SetHealth";
constexpr const char *method = "SetHealth";
constexpr const char *critical = "critical";
constexpr const char *warning = "warning";
constexpr const char *ok = "ok";
} // namespace meHealth
static void setMeStatus(uint8_t eventData2, uint8_t eventData3, bool disable)
{
constexpr const std::array<uint8_t, 10> critical = {
0x1, 0x2, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xD, 0xE};
constexpr const std::array<uint8_t, 5> warning = {0x3, 0xA, 0x13, 0x19,
0x1A};
std::string state;
if (std::find(critical.begin(), critical.end(), eventData2) !=
critical.end())
{
state = meHealth::critical;
}
// special case 0x3 as we only care about a few states
else if (eventData2 == 0x3)
{
if (eventData3 <= 0x2)
{
state = meHealth::warning;
}
else
{
return;
}
}
else if (std::find(warning.begin(), warning.end(), eventData2) !=
warning.end())
{
state = meHealth::warning;
}
else
{
return;
}
if (disable)
{
state = meHealth::ok;
}
std::shared_ptr<sdbusplus::asio::connection> dbus = getSdBus();
auto setHealth =
dbus->new_method_call(meHealth::busname, meHealth::path,
meHealth::interface, meHealth::method);
setHealth.append(std::to_string(static_cast<size_t>(eventData2)), state);
try
{
dbus->call(setHealth);
}
catch (sdbusplus::exception_t &)
{
phosphor::logging::log<phosphor::logging::level::ERR>(
"Failed to set ME Health");
}
}
ipmi::RspType<> ipmiSenPlatformEvent(ipmi::message::Payload &p)
{
constexpr const uint8_t meId = 0x2C;
constexpr const uint8_t meSensorNum = 0x17;
constexpr const uint8_t disabled = 0x80;
uint8_t generatorID = 0;
uint8_t evmRev = 0;
uint8_t sensorType = 0;
uint8_t sensorNum = 0;
uint8_t eventType = 0;
uint8_t eventData1 = 0;
std::optional<uint8_t> eventData2 = 0;
std::optional<uint8_t> eventData3 = 0;
// todo: This check is supposed to be based on the incoming channel.
// e.g. system channel will provide upto 8 bytes including generator
// ID, but ipmb channel will provide only up to 7 bytes without the
// generator ID.
// Support for this check is coming in future patches, so for now just base
// it on if the first byte is the EvMRev (0x04).
if (p.size() && p.data()[0] == 0x04)
{
p.unpack(evmRev, sensorType, sensorNum, eventType, eventData1,
eventData2, eventData3);
// todo: the generator ID for this channel is supposed to come from the
// IPMB requesters slave address. Support for this is coming in future
// patches, so for now just assume it is coming from the ME (0x2C).
generatorID = 0x2C;
}
else
{
p.unpack(generatorID, evmRev, sensorType, sensorNum, eventType,
eventData1, eventData2, eventData3);
}
if (!p.fullyUnpacked())
{
return ipmi::responseReqDataLenInvalid();
}
// Send this request to the Redfish hooks to log it as a Redfish message
// instead. There is no need to add it to the SEL, so just return success.
intel_oem::ipmi::sel::checkRedfishHooks(
generatorID, evmRev, sensorType, sensorNum, eventType, eventData1,
eventData2.value_or(0xFF), eventData3.value_or(0xFF));
if (generatorID == meId && sensorNum == meSensorNum && eventData2 &&
eventData3)
{
setMeStatus(*eventData2, *eventData3, (eventType & disabled));
}
return ipmi::responseSuccess();
}
ipmi::RspType<uint8_t, uint8_t, uint8_t, std::optional<uint8_t>>
ipmiSenGetSensorReading(uint8_t sensnum)
{
std::string connection;
std::string path;
auto status = getSensorConnection(sensnum, connection, path);
if (status)
{
return ipmi::response(status);
}
SensorMap sensorMap;
if (!getSensorMap(connection, path, sensorMap))
{
return ipmi::responseResponseError();
}
auto sensorObject = sensorMap.find("xyz.openbmc_project.Sensor.Value");
if (sensorObject == sensorMap.end() ||
sensorObject->second.find("Value") == sensorObject->second.end())
{
return ipmi::responseResponseError();
}
auto &valueVariant = sensorObject->second["Value"];
double reading = std::visit(VariantToDoubleVisitor(), valueVariant);
double max = 0;
double min = 0;
getSensorMaxMin(sensorMap, max, min);
int16_t mValue = 0;
int16_t bValue = 0;
int8_t rExp = 0;
int8_t bExp = 0;
bool bSigned = false;
if (!getSensorAttributes(max, min, mValue, rExp, bValue, bExp, bSigned))
{
return ipmi::responseResponseError();
}
uint8_t value =
scaleIPMIValueFromDouble(reading, mValue, rExp, bValue, bExp, bSigned);
uint8_t operation =
static_cast<uint8_t>(IPMISensorReadingByte2::sensorScanningEnable);
operation |=
static_cast<uint8_t>(IPMISensorReadingByte2::eventMessagesEnable);
uint8_t thresholds = 0;
auto warningObject =
sensorMap.find("xyz.openbmc_project.Sensor.Threshold.Warning");
if (warningObject != sensorMap.end())
{
auto alarmHigh = warningObject->second.find("WarningAlarmHigh");
auto alarmLow = warningObject->second.find("WarningAlarmLow");
if (alarmHigh != warningObject->second.end())
{
if (std::get<bool>(alarmHigh->second))
{
thresholds |= static_cast<uint8_t>(
IPMISensorReadingByte3::upperNonCritical);
}
}
if (alarmLow != warningObject->second.end())
{
if (std::get<bool>(alarmLow->second))
{
thresholds |= static_cast<uint8_t>(
IPMISensorReadingByte3::lowerNonCritical);
}
}
}
auto criticalObject =
sensorMap.find("xyz.openbmc_project.Sensor.Threshold.Critical");
if (criticalObject != sensorMap.end())
{
auto alarmHigh = criticalObject->second.find("CriticalAlarmHigh");
auto alarmLow = criticalObject->second.find("CriticalAlarmLow");
if (alarmHigh != criticalObject->second.end())
{
if (std::get<bool>(alarmHigh->second))
{
thresholds |=
static_cast<uint8_t>(IPMISensorReadingByte3::upperCritical);
}
}
if (alarmLow != criticalObject->second.end())
{
if (std::get<bool>(alarmLow->second))
{
thresholds |=
static_cast<uint8_t>(IPMISensorReadingByte3::lowerCritical);
}
}
}
// no discrete as of today so optional byte is never returned
return ipmi::responseSuccess(value, operation, thresholds, std::nullopt);
}
/** @brief implements the Set Sensor threshold command
* @param sensorNumber - sensor number
* @param lowerNonCriticalThreshMask
* @param lowerCriticalThreshMask
* @param lowerNonRecovThreshMask
* @param upperNonCriticalThreshMask
* @param upperCriticalThreshMask
* @param upperNonRecovThreshMask
* @param reserved
* @param lowerNonCritical - lower non-critical threshold
* @param lowerCritical - Lower critical threshold
* @param lowerNonRecoverable - Lower non recovarable threshold
* @param upperNonCritical - Upper non-critical threshold
* @param upperCritical - Upper critical
* @param upperNonRecoverable - Upper Non-recoverable
*
* @returns IPMI completion code
*/
ipmi::RspType<> ipmiSenSetSensorThresholds(
uint8_t sensorNum, bool lowerNonCriticalThreshMask,
bool lowerCriticalThreshMask, bool lowerNonRecovThreshMask,
bool upperNonCriticalThreshMask, bool upperCriticalThreshMask,
bool upperNonRecovThreshMask, uint2_t reserved, uint8_t lowerNonCritical,
uint8_t lowerCritical, uint8_t lowerNonRecoverable,
uint8_t upperNonCritical, uint8_t upperCritical,
uint8_t upperNonRecoverable)
{
constexpr uint8_t thresholdMask = 0xFF;
if (reserved)
{
return ipmi::responseInvalidFieldRequest();
}
// lower nc and upper nc not suppported on any sensor
if (lowerNonRecovThreshMask || upperNonRecovThreshMask)
{
return ipmi::responseInvalidFieldRequest();
}
// if none of the threshold mask are set, nothing to do
if (!(lowerNonCriticalThreshMask | lowerCriticalThreshMask |
lowerNonRecovThreshMask | upperNonCriticalThreshMask |
upperCriticalThreshMask | upperNonRecovThreshMask))
{
return ipmi::responseSuccess();
}
std::string connection;
std::string path;
ipmi::Cc status = getSensorConnection(sensorNum, connection, path);
if (status)
{
return ipmi::response(status);
}
SensorMap sensorMap;
if (!getSensorMap(connection, path, sensorMap))
{
return ipmi::responseResponseError();
}
double max = 0;
double min = 0;
getSensorMaxMin(sensorMap, max, min);
int16_t mValue = 0;
int16_t bValue = 0;
int8_t rExp = 0;
int8_t bExp = 0;
bool bSigned = false;
if (!getSensorAttributes(max, min, mValue, rExp, bValue, bExp, bSigned))
{
return ipmi::responseResponseError();
}
// store a vector of property name, value to set, and interface
std::vector<std::tuple<std::string, uint8_t, std::string>> thresholdsToSet;
// define the indexes of the tuple
constexpr uint8_t propertyName = 0;
constexpr uint8_t thresholdValue = 1;
constexpr uint8_t interface = 2;
// verifiy all needed fields are present
if (lowerCriticalThreshMask || upperCriticalThreshMask)
{
auto findThreshold =
sensorMap.find("xyz.openbmc_project.Sensor.Threshold.Critical");
if (findThreshold == sensorMap.end())
{
return ipmi::responseInvalidFieldRequest();
}
if (lowerCriticalThreshMask)
{
auto findLower = findThreshold->second.find("CriticalLow");
if (findLower == findThreshold->second.end())
{
return ipmi::responseInvalidFieldRequest();
}
thresholdsToSet.emplace_back("CriticalLow", lowerCritical,
findThreshold->first);
}
if (upperCriticalThreshMask)
{
auto findUpper = findThreshold->second.find("CriticalHigh");
if (findUpper == findThreshold->second.end())
{
return ipmi::responseInvalidFieldRequest();
}
thresholdsToSet.emplace_back("CriticalHigh", upperCritical,
findThreshold->first);
}
}
if (lowerNonCriticalThreshMask || upperNonCriticalThreshMask)
{
auto findThreshold =
sensorMap.find("xyz.openbmc_project.Sensor.Threshold.Warning");
if (findThreshold == sensorMap.end())
{
return ipmi::responseInvalidFieldRequest();
}
if (lowerNonCriticalThreshMask)
{
auto findLower = findThreshold->second.find("WarningLow");
if (findLower == findThreshold->second.end())
{
return ipmi::responseInvalidFieldRequest();
}
thresholdsToSet.emplace_back("WarningLow", lowerNonCritical,
findThreshold->first);
}
if (upperNonCriticalThreshMask)
{
auto findUpper = findThreshold->second.find("WarningHigh");
if (findUpper == findThreshold->second.end())
{
return ipmi::responseInvalidFieldRequest();
}
thresholdsToSet.emplace_back("WarningHigh", upperNonCritical,
findThreshold->first);
}
}
for (const auto &property : thresholdsToSet)
{
// from section 36.3 in the IPMI Spec, assume all linear
double valueToSet = ((mValue * std::get<thresholdValue>(property)) +
(bValue * std::pow(10, bExp))) *
std::pow(10, rExp);
setDbusProperty(
*getSdBus(), connection, path, std::get<interface>(property),
std::get<propertyName>(property), ipmi::Value(valueToSet));
}
return ipmi::responseSuccess();
}
IPMIThresholds getIPMIThresholds(const SensorMap &sensorMap)
{
IPMIThresholds resp;
auto warningInterface =
sensorMap.find("xyz.openbmc_project.Sensor.Threshold.Warning");
auto criticalInterface =
sensorMap.find("xyz.openbmc_project.Sensor.Threshold.Critical");
if ((warningInterface != sensorMap.end()) ||
(criticalInterface != sensorMap.end()))
{
auto sensorPair = sensorMap.find("xyz.openbmc_project.Sensor.Value");
if (sensorPair == sensorMap.end())
{
// should not have been able to find a sensor not implementing
// the sensor object
throw std::runtime_error("Invalid sensor map");
}
double max = 0;
double min = 0;
getSensorMaxMin(sensorMap, max, min);
int16_t mValue = 0;
int16_t bValue = 0;
int8_t rExp = 0;
int8_t bExp = 0;
bool bSigned = false;
if (!getSensorAttributes(max, min, mValue, rExp, bValue, bExp, bSigned))
{
throw std::runtime_error("Invalid sensor atrributes");
}
if (warningInterface != sensorMap.end())
{
auto &warningMap = warningInterface->second;
auto warningHigh = warningMap.find("WarningHigh");
auto warningLow = warningMap.find("WarningLow");
if (warningHigh != warningMap.end())
{
double value =
std::visit(VariantToDoubleVisitor(), warningHigh->second);
resp.warningHigh = scaleIPMIValueFromDouble(
value, mValue, rExp, bValue, bExp, bSigned);
}
if (warningLow != warningMap.end())
{
double value =
std::visit(VariantToDoubleVisitor(), warningLow->second);
resp.warningLow = scaleIPMIValueFromDouble(
value, mValue, rExp, bValue, bExp, bSigned);
}
}
if (criticalInterface != sensorMap.end())
{
auto &criticalMap = criticalInterface->second;
auto criticalHigh = criticalMap.find("CriticalHigh");
auto criticalLow = criticalMap.find("CriticalLow");
if (criticalHigh != criticalMap.end())
{
double value =
std::visit(VariantToDoubleVisitor(), criticalHigh->second);
resp.criticalHigh = scaleIPMIValueFromDouble(
value, mValue, rExp, bValue, bExp, bSigned);
}
if (criticalLow != criticalMap.end())
{
double value =
std::visit(VariantToDoubleVisitor(), criticalLow->second);
resp.criticalLow = scaleIPMIValueFromDouble(
value, mValue, rExp, bValue, bExp, bSigned);
}
}
}
return resp;
}
ipmi::RspType<uint8_t, // readable
uint8_t, // lowerNCrit
uint8_t, // lowerCrit
uint8_t, // lowerNrecoverable
uint8_t, // upperNC
uint8_t, // upperCrit
uint8_t> // upperNRecoverable
ipmiSenGetSensorThresholds(uint8_t sensorNumber)
{
std::string connection;
std::string path;
auto status = getSensorConnection(sensorNumber, connection, path);
if (status)
{
return ipmi::response(status);
}
SensorMap sensorMap;
if (!getSensorMap(connection, path, sensorMap))
{
return ipmi::responseResponseError();
}
IPMIThresholds thresholdData;
try
{
thresholdData = getIPMIThresholds(sensorMap);
}
catch (std::exception &)
{
return ipmi::responseResponseError();
}
uint8_t readable = 0;
uint8_t lowerNC = 0;
uint8_t lowerCritical = 0;
uint8_t lowerNonRecoverable = 0;
uint8_t upperNC = 0;
uint8_t upperCritical = 0;
uint8_t upperNonRecoverable = 0;
if (thresholdData.warningHigh)
{
readable |=
1 << static_cast<uint8_t>(IPMIThresholdRespBits::upperNonCritical);
upperNC = *thresholdData.warningHigh;
}
if (thresholdData.warningLow)
{
readable |=
1 << static_cast<uint8_t>(IPMIThresholdRespBits::lowerNonCritical);
lowerNC = *thresholdData.warningLow;
}
if (thresholdData.criticalHigh)
{
readable |=
1 << static_cast<uint8_t>(IPMIThresholdRespBits::upperCritical);
upperCritical = *thresholdData.criticalHigh;
}
if (thresholdData.criticalLow)
{
readable |=
1 << static_cast<uint8_t>(IPMIThresholdRespBits::lowerCritical);
lowerCritical = *thresholdData.criticalLow;
}
return ipmi::responseSuccess(readable, lowerNC, lowerCritical,
lowerNonRecoverable, upperNC, upperCritical,
upperNonRecoverable);
}
/** @brief implements the get Sensor event enable command
* @param sensorNumber - sensor number
*
* @returns IPMI completion code plus response data
* - enabled - Sensor Event messages
* - assertionEnabledLsb - Assertion event messages
* - assertionEnabledMsb - Assertion event messages
* - deassertionEnabledLsb - Deassertion event messages
* - deassertionEnabledMsb - Deassertion event messages
*/
ipmi::RspType<uint8_t, // enabled
uint8_t, // assertionEnabledLsb
uint8_t, // assertionEnabledMsb
uint8_t, // deassertionEnabledLsb
uint8_t> // deassertionEnabledMsb
ipmiSenGetSensorEventEnable(uint8_t sensorNum)
{
std::string connection;
std::string path;
uint8_t enabled = 0;
uint8_t assertionEnabledLsb = 0;
uint8_t assertionEnabledMsb = 0;
uint8_t deassertionEnabledLsb = 0;
uint8_t deassertionEnabledMsb = 0;
auto status = getSensorConnection(sensorNum, connection, path);
if (status)
{
return ipmi::response(status);
}
SensorMap sensorMap;
if (!getSensorMap(connection, path, sensorMap))
{
return ipmi::responseResponseError();
}
auto warningInterface =
sensorMap.find("xyz.openbmc_project.Sensor.Threshold.Warning");
auto criticalInterface =
sensorMap.find("xyz.openbmc_project.Sensor.Threshold.Critical");
if ((warningInterface != sensorMap.end()) ||
(criticalInterface != sensorMap.end()))
{
enabled = static_cast<uint8_t>(
IPMISensorEventEnableByte2::sensorScanningEnable);
if (warningInterface != sensorMap.end())
{
auto &warningMap = warningInterface->second;
auto warningHigh = warningMap.find("WarningHigh");
auto warningLow = warningMap.find("WarningLow");
if (warningHigh != warningMap.end())
{
assertionEnabledLsb |= static_cast<uint8_t>(
IPMISensorEventEnableThresholds::upperNonCriticalGoingHigh);
deassertionEnabledLsb |= static_cast<uint8_t>(
IPMISensorEventEnableThresholds::upperNonCriticalGoingLow);
}
if (warningLow != warningMap.end())
{
assertionEnabledLsb |= static_cast<uint8_t>(
IPMISensorEventEnableThresholds::lowerNonCriticalGoingLow);
deassertionEnabledLsb |= static_cast<uint8_t>(
IPMISensorEventEnableThresholds::lowerNonCriticalGoingHigh);
}
}
if (criticalInterface != sensorMap.end())
{
auto &criticalMap = criticalInterface->second;
auto criticalHigh = criticalMap.find("CriticalHigh");
auto criticalLow = criticalMap.find("CriticalLow");
if (criticalHigh != criticalMap.end())
{
assertionEnabledMsb |= static_cast<uint8_t>(
IPMISensorEventEnableThresholds::upperCriticalGoingHigh);
deassertionEnabledMsb |= static_cast<uint8_t>(
IPMISensorEventEnableThresholds::upperCriticalGoingLow);
}
if (criticalLow != criticalMap.end())
{
assertionEnabledLsb |= static_cast<uint8_t>(
IPMISensorEventEnableThresholds::lowerCriticalGoingLow);
deassertionEnabledLsb |= static_cast<uint8_t>(
IPMISensorEventEnableThresholds::lowerCriticalGoingHigh);
}
}
}
return ipmi::responseSuccess(enabled, assertionEnabledLsb,
assertionEnabledMsb, deassertionEnabledLsb,
deassertionEnabledMsb);
}
ipmi_ret_t ipmiSenGetSensorEventStatus(ipmi_netfn_t netfn, ipmi_cmd_t cmd,
ipmi_request_t request,
ipmi_response_t response,
ipmi_data_len_t dataLen,
ipmi_context_t context)
{
if (*dataLen != 1)
{
*dataLen = 0;
return IPMI_CC_REQ_DATA_LEN_INVALID;
}
*dataLen = 0; // default to 0 in case of an error
uint8_t sensnum = *(static_cast<uint8_t *>(request));
std::string connection;
std::string path;
auto status = getSensorConnection(sensnum, connection, path);
if (status)
{
return status;
}
SensorMap sensorMap;
if (!getSensorMap(connection, path, sensorMap))
{
return IPMI_CC_RESPONSE_ERROR;
}
auto warningInterface =
sensorMap.find("xyz.openbmc_project.Sensor.Threshold.Warning");
auto criticalInterface =
sensorMap.find("xyz.openbmc_project.Sensor.Threshold.Critical");
// zero out response buff
auto responseClear = static_cast<uint8_t *>(response);
std::fill(responseClear, responseClear + sizeof(SensorEventStatusResp), 0);
auto resp = static_cast<SensorEventStatusResp *>(response);
resp->enabled =
static_cast<uint8_t>(IPMISensorEventEnableByte2::sensorScanningEnable);
std::optional<bool> criticalDeassertHigh =
thresholdDeassertMap[path]["CriticalAlarmHigh"];
std::optional<bool> criticalDeassertLow =
thresholdDeassertMap[path]["CriticalAlarmLow"];
std::optional<bool> warningDeassertHigh =
thresholdDeassertMap[path]["WarningAlarmHigh"];
std::optional<bool> warningDeassertLow =
thresholdDeassertMap[path]["WarningAlarmLow"];
if (criticalDeassertHigh && !*criticalDeassertHigh)
{
resp->deassertionsMSB |= static_cast<uint8_t>(
IPMISensorEventEnableThresholds::upperCriticalGoingHigh);
}
if (criticalDeassertLow && !*criticalDeassertLow)
{
resp->deassertionsMSB |= static_cast<uint8_t>(
IPMISensorEventEnableThresholds::upperCriticalGoingLow);
}
if (warningDeassertHigh && !*warningDeassertHigh)
{
resp->deassertionsLSB |= static_cast<uint8_t>(
IPMISensorEventEnableThresholds::upperNonCriticalGoingHigh);
}
if (warningDeassertLow && !*warningDeassertLow)
{
resp->deassertionsLSB |= static_cast<uint8_t>(
IPMISensorEventEnableThresholds::lowerNonCriticalGoingHigh);
}
if ((warningInterface != sensorMap.end()) ||
(criticalInterface != sensorMap.end()))
{
resp->enabled = static_cast<uint8_t>(
IPMISensorEventEnableByte2::eventMessagesEnable);
if (warningInterface != sensorMap.end())
{
auto &warningMap = warningInterface->second;
auto warningHigh = warningMap.find("WarningAlarmHigh");
auto warningLow = warningMap.find("WarningAlarmLow");
auto warningHighAlarm = false;
auto warningLowAlarm = false;
if (warningHigh != warningMap.end())
{
warningHighAlarm = std::get<bool>(warningHigh->second);
}
if (warningLow != warningMap.end())
{
warningLowAlarm = std::get<bool>(warningLow->second);
}
if (warningHighAlarm)
{
resp->assertionsLSB |= static_cast<uint8_t>(
IPMISensorEventEnableThresholds::upperNonCriticalGoingHigh);
}
if (warningLowAlarm)
{
resp->assertionsLSB |= 1; // lower nc going low
}
}
if (criticalInterface != sensorMap.end())
{
auto &criticalMap = criticalInterface->second;
auto criticalHigh = criticalMap.find("CriticalAlarmHigh");
auto criticalLow = criticalMap.find("CriticalAlarmLow");
auto criticalHighAlarm = false;
auto criticalLowAlarm = false;
if (criticalHigh != criticalMap.end())
{
criticalHighAlarm = std::get<bool>(criticalHigh->second);
}
if (criticalLow != criticalMap.end())
{
criticalLowAlarm = std::get<bool>(criticalLow->second);
}
if (criticalHighAlarm)
{
resp->assertionsMSB |= static_cast<uint8_t>(
IPMISensorEventEnableThresholds::upperCriticalGoingHigh);
}
if (criticalLowAlarm)
{
resp->assertionsLSB |= static_cast<uint8_t>(
IPMISensorEventEnableThresholds::lowerCriticalGoingLow);
}
}
*dataLen = sizeof(SensorEventStatusResp);
}
// no thresholds enabled, don't need assertionMSB
else
{
*dataLen = sizeof(SensorEventStatusResp) - 1;
}
return IPMI_CC_OK;
}
/* end sensor commands */
/* storage commands */
ipmi::RspType<uint8_t, // sdr version
uint16_t, // record count
uint16_t, // free space
uint32_t, // most recent addition
uint32_t, // most recent erase
uint8_t // operationSupport
>
ipmiStorageGetSDRRepositoryInfo(void)
{
constexpr const uint16_t unspecifiedFreeSpace = 0xFFFF;
if (sensorTree.empty() && !getSensorSubtree(sensorTree))
{
return ipmi::responseResponseError();
}
size_t fruCount = 0;
ipmi::Cc ret = ipmi::storage::getFruSdrCount(fruCount);
if (ret != ipmi::ccSuccess)
{
return ipmi::response(ret);
}
uint16_t recordCount =
sensorTree.size() + fruCount + ipmi::storage::type12Count;
uint8_t operationSupport = static_cast<uint8_t>(
SdrRepositoryInfoOps::overflow); // write not supported
operationSupport |=
static_cast<uint8_t>(SdrRepositoryInfoOps::allocCommandSupported);
operationSupport |= static_cast<uint8_t>(
SdrRepositoryInfoOps::reserveSDRRepositoryCommandSupported);
return ipmi::responseSuccess(ipmiSdrVersion, recordCount,
unspecifiedFreeSpace, sdrLastAdd,
sdrLastRemove, operationSupport);
}
/** @brief implements the get SDR allocation info command
*
* @returns IPMI completion code plus response data
* - allocUnits - Number of possible allocation units
* - allocUnitSize - Allocation unit size in bytes.
* - allocUnitFree - Number of free allocation units
* - allocUnitLargestFree - Largest free block in allocation units
* - maxRecordSize - Maximum record size in allocation units.
*/
ipmi::RspType<uint16_t, // allocUnits
uint16_t, // allocUnitSize
uint16_t, // allocUnitFree
uint16_t, // allocUnitLargestFree
uint8_t // maxRecordSize
>
ipmiStorageGetSDRAllocationInfo()
{
// 0000h unspecified number of alloc units
constexpr uint16_t allocUnits = 0;
constexpr uint16_t allocUnitFree = 0;
constexpr uint16_t allocUnitLargestFree = 0;
// only allow one block at a time
constexpr uint8_t maxRecordSize = 1;
return ipmi::responseSuccess(allocUnits, maxSDRTotalSize, allocUnitFree,
allocUnitLargestFree, maxRecordSize);
}
/** @brief implements the reserve SDR command
* @returns IPMI completion code plus response data
* - sdrReservationID
*/
ipmi::RspType<uint16_t> ipmiStorageReserveSDR()
{
sdrReservationID++;
if (sdrReservationID == 0)
{
sdrReservationID++;
}
return ipmi::responseSuccess(sdrReservationID);
}
ipmi::RspType<uint16_t, // next record ID
std::vector<uint8_t> // payload
>
ipmiStorageGetSDR(uint16_t reservationID, uint16_t recordID, uint8_t offset,
uint8_t bytesToRead)
{
constexpr uint16_t lastRecordIndex = 0xFFFF;
// reservation required for partial reads with non zero offset into
// record
if ((sdrReservationID == 0 || reservationID != sdrReservationID) && offset)
{
return ipmi::responseInvalidReservationId();
}
if (sensorTree.empty() && !getSensorSubtree(sensorTree))
{
return ipmi::responseResponseError();
}
size_t fruCount = 0;
ipmi::Cc ret = ipmi::storage::getFruSdrCount(fruCount);
if (ret != ipmi::ccSuccess)
{
return ipmi::response(ret);
}
size_t lastRecord =
sensorTree.size() + fruCount + ipmi::storage::type12Count - 1;
if (recordID == lastRecordIndex)
{
recordID = lastRecord;
}
if (recordID > lastRecord)
{
return ipmi::responseInvalidFieldRequest();
}
uint16_t nextRecordId = lastRecord > recordID ? recordID + 1 : 0XFFFF;
if (recordID >= sensorTree.size())
{
std::vector<uint8_t> recordData;
size_t fruIndex = recordID - sensorTree.size();
if (fruIndex >= fruCount)
{
// handle type 12 hardcoded records
size_t type12Index = fruIndex - fruCount;
if (type12Index >= ipmi::storage::type12Count ||
offset > sizeof(Type12Record))
{
return ipmi::responseInvalidFieldRequest();
}
std::vector<uint8_t> record =
ipmi::storage::getType12SDRs(type12Index, recordID);
if (record.size() < (offset + bytesToRead))
{
bytesToRead = record.size() - offset;
}
recordData.insert(recordData.end(), record.begin() + offset,
record.begin() + offset + bytesToRead);
}
else
{
// handle fru records
get_sdr::SensorDataFruRecord data;
if (offset > sizeof(data))
{
return ipmi::responseInvalidFieldRequest();
}
ret = ipmi::storage::getFruSdrs(fruIndex, data);
if (ret != IPMI_CC_OK)
{
return ipmi::response(ret);
}
data.header.record_id_msb = recordID << 8;
data.header.record_id_lsb = recordID & 0xFF;
if (sizeof(data) < (offset + bytesToRead))
{
bytesToRead = sizeof(data) - offset;
}
uint8_t *respStart = reinterpret_cast<uint8_t *>(&data) + offset;
recordData.insert(recordData.end(), respStart,
respStart + bytesToRead);
}
return ipmi::responseSuccess(nextRecordId, recordData);
}
std::string connection;
std::string path;
uint16_t sensorIndex = recordID;
for (const auto &sensor : sensorTree)
{
if (sensorIndex-- == 0)
{
if (!sensor.second.size())
{
return ipmi::responseResponseError();
}
connection = sensor.second.begin()->first;
path = sensor.first;
break;
}
}
SensorMap sensorMap;
if (!getSensorMap(connection, path, sensorMap))
{
return ipmi::responseResponseError();
}
uint8_t sensornumber = (recordID & 0xFF);
get_sdr::SensorDataFullRecord record = {0};
record.header.record_id_msb = recordID << 8;
record.header.record_id_lsb = recordID & 0xFF;
record.header.sdr_version = ipmiSdrVersion;
record.header.record_type = get_sdr::SENSOR_DATA_FULL_RECORD;
record.header.record_length = sizeof(get_sdr::SensorDataFullRecord) -
sizeof(get_sdr::SensorDataRecordHeader);
record.key.owner_id = 0x20;
record.key.owner_lun = 0x0;
record.key.sensor_number = sensornumber;
record.body.entity_id = 0x0;
record.body.entity_instance = 0x01;
record.body.sensor_capabilities = 0x68; // auto rearm - todo hysteresis
record.body.sensor_type = getSensorTypeFromPath(path);
std::string type = getSensorTypeStringFromPath(path);
auto typeCstr = type.c_str();
auto findUnits = sensorUnits.find(typeCstr);
if (findUnits != sensorUnits.end())
{
record.body.sensor_units_2_base =
static_cast<uint8_t>(findUnits->second);
} // else default 0x0 unspecified
record.body.event_reading_type = getSensorEventTypeFromPath(path);
auto sensorObject = sensorMap.find("xyz.openbmc_project.Sensor.Value");
if (sensorObject == sensorMap.end())
{
return ipmi::responseResponseError();
}
auto maxObject = sensorObject->second.find("MaxValue");
auto minObject = sensorObject->second.find("MinValue");
double max = 128;
double min = -127;
if (maxObject != sensorObject->second.end())
{
max = std::visit(VariantToDoubleVisitor(), maxObject->second);
}
if (minObject != sensorObject->second.end())
{
min = std::visit(VariantToDoubleVisitor(), minObject->second);
}
int16_t mValue = 0;
int8_t rExp = 0;
int16_t bValue = 0;
int8_t bExp = 0;
bool bSigned = false;
if (!getSensorAttributes(max, min, mValue, rExp, bValue, bExp, bSigned))
{
return ipmi::responseResponseError();
}
// apply M, B, and exponents, M and B are 10 bit values, exponents are 4
record.body.m_lsb = mValue & 0xFF;
// move the smallest bit of the MSB into place (bit 9)
// the MSbs are bits 7:8 in m_msb_and_tolerance
uint8_t mMsb = (mValue & (1 << 8)) > 0 ? (1 << 6) : 0;
// assign the negative
if (mValue < 0)
{
mMsb |= (1 << 7);
}
record.body.m_msb_and_tolerance = mMsb;
record.body.b_lsb = bValue & 0xFF;
// move the smallest bit of the MSB into place
// the MSbs are bits 7:8 in b_msb_and_accuracy_lsb
uint8_t bMsb = (bValue & (1 << 8)) > 0 ? (1 << 6) : 0;
// assign the negative
if (bValue < 0)
{
bMsb |= (1 << 7);
}
record.body.b_msb_and_accuracy_lsb = bMsb;
record.body.r_b_exponents = bExp & 0x7;
if (bExp < 0)
{
record.body.r_b_exponents |= 1 << 3;
}
record.body.r_b_exponents = (rExp & 0x7) << 4;
if (rExp < 0)
{
record.body.r_b_exponents |= 1 << 7;
}
// todo fill out rest of units
if (bSigned)
{
record.body.sensor_units_1 = 1 << 7;
}
// populate sensor name from path
std::string name;
size_t nameStart = path.rfind("/");
if (nameStart != std::string::npos)
{
name = path.substr(nameStart + 1, std::string::npos - nameStart);
}
std::replace(name.begin(), name.end(), '_', ' ');
if (name.size() > FULL_RECORD_ID_STR_MAX_LENGTH)
{
// try to not truncate by replacing common words
constexpr std::array<std::pair<const char *, const char *>, 2>
replaceWords = {std::make_pair("Output", "Out"),
std::make_pair("Input", "In")};
for (const auto &[find, replace] : replaceWords)
{
boost::replace_all(name, find, replace);
}
name.resize(FULL_RECORD_ID_STR_MAX_LENGTH);
}
record.body.id_string_info = name.size();
std::strncpy(record.body.id_string, name.c_str(),
sizeof(record.body.id_string));
IPMIThresholds thresholdData;
try
{
thresholdData = getIPMIThresholds(sensorMap);
}
catch (std::exception &)
{
return ipmi::responseResponseError();
}
if (thresholdData.criticalHigh)
{
record.body.upper_critical_threshold = *thresholdData.criticalHigh;
record.body.supported_deassertions[1] |= static_cast<uint8_t>(
IPMISensorEventEnableThresholds::upperCriticalGoingHigh);
record.body.supported_assertions[1] |= static_cast<uint8_t>(
IPMISensorEventEnableThresholds::upperCriticalGoingHigh);
record.body.discrete_reading_setting_mask[0] |=
static_cast<uint8_t>(IPMISensorReadingByte3::upperCritical);
}
if (thresholdData.warningHigh)
{
record.body.upper_noncritical_threshold = *thresholdData.warningHigh;
record.body.supported_deassertions[0] |= static_cast<uint8_t>(
IPMISensorEventEnableThresholds::upperNonCriticalGoingHigh);
record.body.supported_assertions[0] |= static_cast<uint8_t>(
IPMISensorEventEnableThresholds::upperNonCriticalGoingHigh);
record.body.discrete_reading_setting_mask[0] |=
static_cast<uint8_t>(IPMISensorReadingByte3::upperNonCritical);
}
if (thresholdData.criticalLow)
{
record.body.lower_critical_threshold = *thresholdData.criticalLow;
record.body.supported_deassertions[0] |= static_cast<uint8_t>(
IPMISensorEventEnableThresholds::lowerCriticalGoingLow);
record.body.supported_assertions[0] |= static_cast<uint8_t>(
IPMISensorEventEnableThresholds::lowerCriticalGoingLow);
record.body.discrete_reading_setting_mask[0] |=
static_cast<uint8_t>(IPMISensorReadingByte3::lowerCritical);
}
if (thresholdData.warningLow)
{
record.body.lower_noncritical_threshold = *thresholdData.warningLow;
record.body.supported_deassertions[0] |= static_cast<uint8_t>(
IPMISensorEventEnableThresholds::lowerNonCriticalGoingLow);
record.body.supported_assertions[0] |= static_cast<uint8_t>(
IPMISensorEventEnableThresholds::lowerNonCriticalGoingLow);
record.body.discrete_reading_setting_mask[0] |=
static_cast<uint8_t>(IPMISensorReadingByte3::lowerNonCritical);
}
// everything that is readable is setable
record.body.discrete_reading_setting_mask[1] =
record.body.discrete_reading_setting_mask[0];
if (sizeof(get_sdr::SensorDataFullRecord) < (offset + bytesToRead))
{
bytesToRead = sizeof(get_sdr::SensorDataFullRecord) - offset;
}
uint8_t *respStart = reinterpret_cast<uint8_t *>(&record) + offset;
std::vector<uint8_t> recordData(respStart, respStart + bytesToRead);
return ipmi::responseSuccess(nextRecordId, recordData);
}
/* end storage commands */
void registerSensorFunctions()
{
// get firmware version information
ipmiPrintAndRegister(NETFUN_SENSOR, IPMI_CMD_WILDCARD, nullptr,
ipmiSensorWildcardHandler, PRIVILEGE_USER);
// <Get Sensor Type>
ipmiPrintAndRegister(NETFUN_SENSOR, ipmi::sensor_event::cmdGetSensorType,
nullptr, ipmiSensorWildcardHandler, PRIVILEGE_USER);
// <Set Sensor Reading and Event Status>
ipmiPrintAndRegister(
NETFUN_SENSOR, ipmi::sensor_event::cmdSetSensorReadingAndEvtSts,
nullptr, ipmiSensorWildcardHandler, PRIVILEGE_OPERATOR);
// <Platform Event>
ipmi::registerHandler(ipmi::prioOemBase, ipmi::netFnSensor,
ipmi::sensor_event::cmdPlatformEvent,
ipmi::Privilege::Operator, ipmiSenPlatformEvent);
// <Get Sensor Reading>
ipmi::registerHandler(ipmi::prioOemBase, ipmi::netFnSensor,
ipmi::sensor_event::cmdGetSensorReading,
ipmi::Privilege::User, ipmiSenGetSensorReading);
// <Get Sensor Threshold>
ipmi::registerHandler(ipmi::prioOemBase, ipmi::netFnSensor,
ipmi::sensor_event::cmdGetSensorThreshold,
ipmi::Privilege::User, ipmiSenGetSensorThresholds);
// <Set Sensor Threshold>
ipmi::registerHandler(ipmi::prioOemBase, ipmi::netFnSensor,
ipmi::sensor_event::cmdSetSensorThreshold,
ipmi::Privilege::Operator,
ipmiSenSetSensorThresholds);
// <Get Sensor Event Enable>
ipmi::registerHandler(ipmi::prioOemBase, ipmi::netFnSensor,
ipmi::sensor_event::cmdGetSensorEventEnable,
ipmi::Privilege::User, ipmiSenGetSensorEventEnable);
// <Get Sensor Event Status>
ipmiPrintAndRegister(NETFUN_SENSOR,
ipmi::sensor_event::cmdGetSensorEventStatus, nullptr,
ipmiSenGetSensorEventStatus, PRIVILEGE_USER);
// register all storage commands for both Sensor and Storage command
// versions
// <Get SDR Repository Info>
ipmi::registerHandler(ipmi::prioOemBase, ipmi::netFnStorage,
ipmi::storage::cmdGetSdrRepositoryInfo,
ipmi::Privilege::User,
ipmiStorageGetSDRRepositoryInfo);
// <Get SDR Allocation Info>
ipmi::registerHandler(ipmi::prioOemBase, ipmi::netFnStorage,
ipmi::storage::cmdGetSdrRepositoryAllocInfo,
ipmi::Privilege::User,
ipmiStorageGetSDRAllocationInfo);
// <Reserve SDR Repo>
ipmi::registerHandler(ipmi::prioOemBase, ipmi::netFnSensor,
ipmi::sensor_event::cmdReserveDeviceSdrRepository,
ipmi::Privilege::User, ipmiStorageReserveSDR);
ipmi::registerHandler(ipmi::prioOemBase, ipmi::netFnStorage,
ipmi::storage::cmdReserveSdrRepository,
ipmi::Privilege::User, ipmiStorageReserveSDR);
// <Get Sdr>
ipmi::registerHandler(ipmi::prioOemBase, ipmi::netFnSensor,
ipmi::sensor_event::cmdGetDeviceSdr,
ipmi::Privilege::User, ipmiStorageGetSDR);
ipmi::registerHandler(ipmi::prioOemBase, ipmi::netFnStorage,
ipmi::storage::cmdGetSdr, ipmi::Privilege::User,
ipmiStorageGetSDR);
}
} // namespace ipmi