/* // 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 "sensorcommands.hpp" #include "commandutils.hpp" #include "ipmi_to_redfish_hooks.hpp" #include "sdrutils.hpp" #include "sensorutils.hpp" #include "storagecommands.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace ipmi { using ManagedObjectType = std::map>>; using SensorMap = std::map>; 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 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 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::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::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>> 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> 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(&(findAssert->second)); if (ptr == nullptr) { phosphor::logging::log( "thresholdChanged: Assert non bool"); return; } if (*ptr) { phosphor::logging::log( "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( "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> updateTimeMap; auto updateFind = updateTimeMap.find(sensorConnection); auto lastUpdate = std::chrono::time_point(); if (updateFind != updateTimeMap.end()) { lastUpdate = updateFind->second; } auto now = std::chrono::steady_clock::now(); if (std::chrono::duration_cast(now - lastUpdate) .count() > sensorMapUpdatePeriod) { updateTimeMap[sensorConnection] = now; std::shared_ptr 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( "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 critical = { 0x1, 0x2, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xD, 0xE}; constexpr const std::array 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 dbus = getSdBus(); auto setHealth = dbus->new_method_call(meHealth::busname, meHealth::path, meHealth::interface, meHealth::method); setHealth.append(std::to_string(static_cast(eventData2)), state); try { dbus->call(setHealth); } catch (sdbusplus::exception_t &) { phosphor::logging::log( "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 eventData2 = 0; std::optional 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> 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(IPMISensorReadingByte2::sensorScanningEnable); operation |= static_cast(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(alarmHigh->second)) { thresholds |= static_cast( IPMISensorReadingByte3::upperNonCritical); } } if (alarmLow != warningObject->second.end()) { if (std::get(alarmLow->second)) { thresholds |= static_cast( 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(alarmHigh->second)) { thresholds |= static_cast(IPMISensorReadingByte3::upperCritical); } } if (alarmLow != criticalObject->second.end()) { if (std::get(alarmLow->second)) { thresholds |= static_cast(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> 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(property)) + (bValue * std::pow(10, bExp))) * std::pow(10, rExp); setDbusProperty( *getSdBus(), connection, path, std::get(property), std::get(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 // 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(IPMIThresholdRespBits::upperNonCritical); upperNC = *thresholdData.warningHigh; } if (thresholdData.warningLow) { readable |= 1 << static_cast(IPMIThresholdRespBits::lowerNonCritical); lowerNC = *thresholdData.warningLow; } if (thresholdData.criticalHigh) { readable |= 1 << static_cast(IPMIThresholdRespBits::upperCritical); upperCritical = *thresholdData.criticalHigh; } if (thresholdData.criticalLow) { readable |= 1 << static_cast(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 // 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( 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( IPMISensorEventEnableThresholds::upperNonCriticalGoingHigh); deassertionEnabledLsb |= static_cast( IPMISensorEventEnableThresholds::upperNonCriticalGoingLow); } if (warningLow != warningMap.end()) { assertionEnabledLsb |= static_cast( IPMISensorEventEnableThresholds::lowerNonCriticalGoingLow); deassertionEnabledLsb |= static_cast( 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( IPMISensorEventEnableThresholds::upperCriticalGoingHigh); deassertionEnabledMsb |= static_cast( IPMISensorEventEnableThresholds::upperCriticalGoingLow); } if (criticalLow != criticalMap.end()) { assertionEnabledLsb |= static_cast( IPMISensorEventEnableThresholds::lowerCriticalGoingLow); deassertionEnabledLsb |= static_cast( 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(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(response); std::fill(responseClear, responseClear + sizeof(SensorEventStatusResp), 0); auto resp = static_cast(response); resp->enabled = static_cast(IPMISensorEventEnableByte2::sensorScanningEnable); std::optional criticalDeassertHigh = thresholdDeassertMap[path]["CriticalAlarmHigh"]; std::optional criticalDeassertLow = thresholdDeassertMap[path]["CriticalAlarmLow"]; std::optional warningDeassertHigh = thresholdDeassertMap[path]["WarningAlarmHigh"]; std::optional warningDeassertLow = thresholdDeassertMap[path]["WarningAlarmLow"]; if (criticalDeassertHigh && !*criticalDeassertHigh) { resp->deassertionsMSB |= static_cast( IPMISensorEventEnableThresholds::upperCriticalGoingHigh); } if (criticalDeassertLow && !*criticalDeassertLow) { resp->deassertionsMSB |= static_cast( IPMISensorEventEnableThresholds::upperCriticalGoingLow); } if (warningDeassertHigh && !*warningDeassertHigh) { resp->deassertionsLSB |= static_cast( IPMISensorEventEnableThresholds::upperNonCriticalGoingHigh); } if (warningDeassertLow && !*warningDeassertLow) { resp->deassertionsLSB |= static_cast( IPMISensorEventEnableThresholds::lowerNonCriticalGoingHigh); } if ((warningInterface != sensorMap.end()) || (criticalInterface != sensorMap.end())) { resp->enabled = static_cast( 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(warningHigh->second); } if (warningLow != warningMap.end()) { warningLowAlarm = std::get(warningLow->second); } if (warningHighAlarm) { resp->assertionsLSB |= static_cast( 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(criticalHigh->second); } if (criticalLow != criticalMap.end()) { criticalLowAlarm = std::get(criticalLow->second); } if (criticalHighAlarm) { resp->assertionsMSB |= static_cast( IPMISensorEventEnableThresholds::upperCriticalGoingHigh); } if (criticalLowAlarm) { resp->assertionsLSB |= static_cast( 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 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( SdrRepositoryInfoOps::overflow); // write not supported operationSupport |= static_cast(SdrRepositoryInfoOps::allocCommandSupported); operationSupport |= static_cast( 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 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 ipmiStorageReserveSDR() { sdrReservationID++; if (sdrReservationID == 0) { sdrReservationID++; } return ipmi::responseSuccess(sdrReservationID); } ipmi::RspType // 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 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 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(&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(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, 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( IPMISensorEventEnableThresholds::upperCriticalGoingHigh); record.body.supported_assertions[1] |= static_cast( IPMISensorEventEnableThresholds::upperCriticalGoingHigh); record.body.discrete_reading_setting_mask[0] |= static_cast(IPMISensorReadingByte3::upperCritical); } if (thresholdData.warningHigh) { record.body.upper_noncritical_threshold = *thresholdData.warningHigh; record.body.supported_deassertions[0] |= static_cast( IPMISensorEventEnableThresholds::upperNonCriticalGoingHigh); record.body.supported_assertions[0] |= static_cast( IPMISensorEventEnableThresholds::upperNonCriticalGoingHigh); record.body.discrete_reading_setting_mask[0] |= static_cast(IPMISensorReadingByte3::upperNonCritical); } if (thresholdData.criticalLow) { record.body.lower_critical_threshold = *thresholdData.criticalLow; record.body.supported_deassertions[0] |= static_cast( IPMISensorEventEnableThresholds::lowerCriticalGoingLow); record.body.supported_assertions[0] |= static_cast( IPMISensorEventEnableThresholds::lowerCriticalGoingLow); record.body.discrete_reading_setting_mask[0] |= static_cast(IPMISensorReadingByte3::lowerCritical); } if (thresholdData.warningLow) { record.body.lower_noncritical_threshold = *thresholdData.warningLow; record.body.supported_deassertions[0] |= static_cast( IPMISensorEventEnableThresholds::lowerNonCriticalGoingLow); record.body.supported_assertions[0] |= static_cast( IPMISensorEventEnableThresholds::lowerNonCriticalGoingLow); record.body.discrete_reading_setting_mask[0] |= static_cast(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(&record) + offset; std::vector 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); // ipmiPrintAndRegister(NETFUN_SENSOR, ipmi::sensor_event::cmdGetSensorType, nullptr, ipmiSensorWildcardHandler, PRIVILEGE_USER); // ipmiPrintAndRegister( NETFUN_SENSOR, ipmi::sensor_event::cmdSetSensorReadingAndEvtSts, nullptr, ipmiSensorWildcardHandler, PRIVILEGE_OPERATOR); // ipmi::registerHandler(ipmi::prioOemBase, ipmi::netFnSensor, ipmi::sensor_event::cmdPlatformEvent, ipmi::Privilege::Operator, ipmiSenPlatformEvent); // ipmi::registerHandler(ipmi::prioOemBase, ipmi::netFnSensor, ipmi::sensor_event::cmdGetSensorReading, ipmi::Privilege::User, ipmiSenGetSensorReading); // ipmi::registerHandler(ipmi::prioOemBase, ipmi::netFnSensor, ipmi::sensor_event::cmdGetSensorThreshold, ipmi::Privilege::User, ipmiSenGetSensorThresholds); // ipmi::registerHandler(ipmi::prioOemBase, ipmi::netFnSensor, ipmi::sensor_event::cmdSetSensorThreshold, ipmi::Privilege::Operator, ipmiSenSetSensorThresholds); // ipmi::registerHandler(ipmi::prioOemBase, ipmi::netFnSensor, ipmi::sensor_event::cmdGetSensorEventEnable, ipmi::Privilege::User, ipmiSenGetSensorEventEnable); // ipmiPrintAndRegister(NETFUN_SENSOR, ipmi::sensor_event::cmdGetSensorEventStatus, nullptr, ipmiSenGetSensorEventStatus, PRIVILEGE_USER); // register all storage commands for both Sensor and Storage command // versions // ipmi::registerHandler(ipmi::prioOemBase, ipmi::netFnStorage, ipmi::storage::cmdGetSdrRepositoryInfo, ipmi::Privilege::User, ipmiStorageGetSDRRepositoryInfo); // ipmi::registerHandler(ipmi::prioOemBase, ipmi::netFnStorage, ipmi::storage::cmdGetSdrRepositoryAllocInfo, ipmi::Privilege::User, ipmiStorageGetSDRAllocationInfo); // 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); // 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