#include "config.h" #include "sensorhandler.hpp" #include "entity_map_json.hpp" #include "fruread.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include static constexpr uint8_t fruInventoryDevice = 0x10; static constexpr uint8_t IPMIFruInventory = 0x02; static constexpr uint8_t BMCSlaveAddress = 0x20; extern int updateSensorRecordFromSSRAESC(const void*); extern sd_bus* bus; namespace ipmi { namespace sensor { extern const IdInfoMap sensors; } // namespace sensor } // namespace ipmi extern const FruMap frus; using namespace phosphor::logging; using InternalFailure = sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure; void register_netfn_sen_functions() __attribute__((constructor)); struct sensorTypemap_t { uint8_t number; uint8_t typecode; char dbusname[32]; }; sensorTypemap_t g_SensorTypeMap[] = { {0x01, 0x6F, "Temp"}, {0x0C, 0x6F, "DIMM"}, {0x0C, 0x6F, "MEMORY_BUFFER"}, {0x07, 0x6F, "PROC"}, {0x07, 0x6F, "CORE"}, {0x07, 0x6F, "CPU"}, {0x0F, 0x6F, "BootProgress"}, {0xe9, 0x09, "OccStatus"}, // E9 is an internal mapping to handle sensor // type code os 0x09 {0xC3, 0x6F, "BootCount"}, {0x1F, 0x6F, "OperatingSystemStatus"}, {0x12, 0x6F, "SYSTEM_EVENT"}, {0xC7, 0x03, "SYSTEM"}, {0xC7, 0x03, "MAIN_PLANAR"}, {0xC2, 0x6F, "PowerCap"}, {0x0b, 0xCA, "PowerSupplyRedundancy"}, {0xDA, 0x03, "TurboAllowed"}, {0xD8, 0xC8, "PowerSupplyDerating"}, {0xFF, 0x00, ""}, }; struct sensor_data_t { uint8_t sennum; } __attribute__((packed)); using SDRCacheMap = std::unordered_map; SDRCacheMap sdrCacheMap __attribute__((init_priority(101))); using SensorThresholdMap = std::unordered_map; SensorThresholdMap sensorThresholdMap __attribute__((init_priority(101))); #ifdef FEATURE_SENSORS_CACHE std::map> sensorAddedMatches __attribute__((init_priority(101))); std::map> sensorUpdatedMatches __attribute__((init_priority(101))); std::map> sensorRemovedMatches __attribute__((init_priority(101))); std::unique_ptr sensorsOwnerMatch __attribute__((init_priority(101))); ipmi::sensor::SensorCacheMap sensorCacheMap __attribute__((init_priority(101))); // It is needed to know which objects belong to which service, so that when a // service exits without interfacesRemoved signal, we could invaildate the cache // that is related to the service. It uses below two variables: // - idToServiceMap records which sensors are known to have a related service; // - serviceToIdMap maps a service to the sensors. using sensorIdToServiceMap = std::unordered_map; sensorIdToServiceMap idToServiceMap __attribute__((init_priority(101))); using sensorServiceToIdMap = std::unordered_map>; sensorServiceToIdMap serviceToIdMap __attribute__((init_priority(101))); static void fillSensorIdServiceMap(const std::string&, const std::string& /*intf*/, uint8_t id, const std::string& service) { if (idToServiceMap.find(id) != idToServiceMap.end()) { return; } idToServiceMap[id] = service; serviceToIdMap[service].insert(id); } static void fillSensorIdServiceMap(const std::string& obj, const std::string& intf, uint8_t id) { if (idToServiceMap.find(id) != idToServiceMap.end()) { return; } try { sdbusplus::bus_t bus{ipmid_get_sd_bus_connection()}; auto service = ipmi::getService(bus, intf, obj); idToServiceMap[id] = service; serviceToIdMap[service].insert(id); } catch (...) { // Ignore } } void initSensorMatches() { using namespace sdbusplus::bus::match::rules; sdbusplus::bus_t bus{ipmid_get_sd_bus_connection()}; for (const auto& s : ipmi::sensor::sensors) { sensorAddedMatches.emplace( s.first, std::make_unique( bus, interfacesAdded() + argNpath(0, s.second.sensorPath), [id = s.first, obj = s.second.sensorPath, intf = s.second.propertyInterfaces.begin()->first]( auto& /*msg*/) { fillSensorIdServiceMap(obj, intf, id); })); sensorRemovedMatches.emplace( s.first, std::make_unique( bus, interfacesRemoved() + argNpath(0, s.second.sensorPath), [id = s.first](auto& /*msg*/) { // Ideally this should work. // But when a service is terminated or crashed, it does not // emit interfacesRemoved signal. In that case it's handled // by sensorsOwnerMatch sensorCacheMap[id].reset(); })); sensorUpdatedMatches.emplace( s.first, std::make_unique( bus, type::signal() + path(s.second.sensorPath) + member("PropertiesChanged"s) + interface("org.freedesktop.DBus.Properties"s), [&s](auto& msg) { fillSensorIdServiceMap( s.second.sensorPath, s.second.propertyInterfaces.begin()->first, s.first); try { // This is signal callback std::string interfaceName; msg.read(interfaceName); ipmi::PropertyMap props; msg.read(props); s.second.getFunc(s.first, s.second, props); } catch (const std::exception& e) { sensorCacheMap[s.first].reset(); } })); } sensorsOwnerMatch = std::make_unique( bus, nameOwnerChanged(), [](auto& msg) { std::string name; std::string oldOwner; std::string newOwner; msg.read(name, oldOwner, newOwner); if (!name.empty() && newOwner.empty()) { // The service exits const auto it = serviceToIdMap.find(name); if (it == serviceToIdMap.end()) { return; } for (const auto& id : it->second) { // Invalidate cache sensorCacheMap[id].reset(); } } }); } #endif int get_bus_for_path(const char* path, char** busname) { return mapper_get_service(bus, path, busname); } // Use a lookup table to find the interface name of a specific sensor // This will be used until an alternative is found. this is the first // step for mapping IPMI int find_openbmc_path(uint8_t num, dbus_interface_t* interface) { int rc; const auto& sensor_it = ipmi::sensor::sensors.find(num); if (sensor_it == ipmi::sensor::sensors.end()) { // The sensor map does not contain the sensor requested return -EINVAL; } const auto& info = sensor_it->second; char* busname = nullptr; rc = get_bus_for_path(info.sensorPath.c_str(), &busname); if (rc < 0) { std::fprintf(stderr, "Failed to get %s busname: %s\n", info.sensorPath.c_str(), busname); goto final; } interface->sensortype = info.sensorType; strcpy(interface->bus, busname); strcpy(interface->path, info.sensorPath.c_str()); // Take the interface name from the beginning of the DbusInterfaceMap. This // works for the Value interface but may not suffice for more complex // sensors. // tracked https://github.com/openbmc/phosphor-host-ipmid/issues/103 strcpy(interface->interface, info.propertyInterfaces.begin()->first.c_str()); interface->sensornumber = num; final: free(busname); return rc; } ///////////////////////////////////////////////////////////////////// // // Routines used by ipmi commands wanting to interact on the dbus // ///////////////////////////////////////////////////////////////////// int set_sensor_dbus_state_s(uint8_t number, const char* method, const char* value) { dbus_interface_t a; int r; sd_bus_error error = SD_BUS_ERROR_NULL; sd_bus_message* m = NULL; r = find_openbmc_path(number, &a); if (r < 0) { std::fprintf(stderr, "Failed to find Sensor 0x%02x\n", number); return 0; } r = sd_bus_message_new_method_call(bus, &m, a.bus, a.path, a.interface, method); if (r < 0) { std::fprintf(stderr, "Failed to create a method call: %s", strerror(-r)); goto final; } r = sd_bus_message_append(m, "v", "s", value); if (r < 0) { std::fprintf(stderr, "Failed to create a input parameter: %s", strerror(-r)); goto final; } r = sd_bus_call(bus, m, 0, &error, NULL); if (r < 0) { std::fprintf(stderr, "Failed to call the method: %s", strerror(-r)); } final: sd_bus_error_free(&error); m = sd_bus_message_unref(m); return 0; } int set_sensor_dbus_state_y(uint8_t number, const char* method, const uint8_t value) { dbus_interface_t a; int r; sd_bus_error error = SD_BUS_ERROR_NULL; sd_bus_message* m = NULL; r = find_openbmc_path(number, &a); if (r < 0) { std::fprintf(stderr, "Failed to find Sensor 0x%02x\n", number); return 0; } r = sd_bus_message_new_method_call(bus, &m, a.bus, a.path, a.interface, method); if (r < 0) { std::fprintf(stderr, "Failed to create a method call: %s", strerror(-r)); goto final; } r = sd_bus_message_append(m, "v", "i", value); if (r < 0) { std::fprintf(stderr, "Failed to create a input parameter: %s", strerror(-r)); goto final; } r = sd_bus_call(bus, m, 0, &error, NULL); if (r < 0) { std::fprintf(stderr, "12 Failed to call the method: %s", strerror(-r)); } final: sd_bus_error_free(&error); m = sd_bus_message_unref(m); return 0; } uint8_t dbus_to_sensor_type(char* p) { sensorTypemap_t* s = g_SensorTypeMap; char r = 0; while (s->number != 0xFF) { if (!strcmp(s->dbusname, p)) { r = s->typecode; break; } s++; } if (s->number == 0xFF) printf("Failed to find Sensor Type %s\n", p); return r; } uint8_t get_type_from_interface(dbus_interface_t dbus_if) { uint8_t type; // This is where sensors that do not exist in dbus but do // exist in the host code stop. This should indicate it // is not a supported sensor if (dbus_if.interface[0] == 0) { return 0; } // Fetch type from interface itself. if (dbus_if.sensortype != 0) { type = dbus_if.sensortype; } else { // Non InventoryItems char* p = strrchr(dbus_if.path, '/'); type = dbus_to_sensor_type(p + 1); } return type; } // Replaces find_sensor uint8_t find_type_for_sensor_number(uint8_t num) { int r; dbus_interface_t dbus_if; r = find_openbmc_path(num, &dbus_if); if (r < 0) { std::fprintf(stderr, "Could not find sensor %d\n", num); return 0; } return get_type_from_interface(dbus_if); } /** * @brief implements the get sensor type command. * @param - sensorNumber * * @return IPMI completion code plus response data on success. * - sensorType * - eventType **/ ipmi::RspType ipmiGetSensorType(uint8_t sensorNumber) { uint8_t sensorType = find_type_for_sensor_number(sensorNumber); if (sensorType == 0) { return ipmi::responseSensorInvalid(); } constexpr uint8_t eventType = 0x6F; return ipmi::responseSuccess(sensorType, eventType); } const std::set analogSensorInterfaces = { "xyz.openbmc_project.Sensor.Value", "xyz.openbmc_project.Control.FanPwm", }; bool isAnalogSensor(const std::string& interface) { return (analogSensorInterfaces.count(interface)); } /** @brief This command is used to set sensorReading. @param - sensorNumber - operation - reading - assertOffset0_7 - assertOffset8_14 - deassertOffset0_7 - deassertOffset8_14 - eventData1 - eventData2 - eventData3 @return completion code on success. **/ ipmi::RspType<> ipmiSetSensorReading(uint8_t sensorNumber, uint8_t operation, uint8_t reading, uint8_t assertOffset0_7, uint8_t assertOffset8_14, uint8_t deassertOffset0_7, uint8_t deassertOffset8_14, uint8_t eventData1, uint8_t eventData2, uint8_t eventData3) { log("IPMI SET_SENSOR", entry("SENSOR_NUM=0x%02x", sensorNumber)); if (sensorNumber == 0xFF) { return ipmi::responseInvalidFieldRequest(); } ipmi::sensor::SetSensorReadingReq cmdData; cmdData.number = sensorNumber; cmdData.operation = operation; cmdData.reading = reading; cmdData.assertOffset0_7 = assertOffset0_7; cmdData.assertOffset8_14 = assertOffset8_14; cmdData.deassertOffset0_7 = deassertOffset0_7; cmdData.deassertOffset8_14 = deassertOffset8_14; cmdData.eventData1 = eventData1; cmdData.eventData2 = eventData2; cmdData.eventData3 = eventData3; // Check if the Sensor Number is present const auto iter = ipmi::sensor::sensors.find(sensorNumber); if (iter == ipmi::sensor::sensors.end()) { updateSensorRecordFromSSRAESC(&sensorNumber); return ipmi::responseSuccess(); } try { if (ipmi::sensor::Mutability::Write != (iter->second.mutability & ipmi::sensor::Mutability::Write)) { log("Sensor Set operation is not allowed", entry("SENSOR_NUM=%d", sensorNumber)); return ipmi::responseIllegalCommand(); } auto ipmiRC = iter->second.updateFunc(cmdData, iter->second); return ipmi::response(ipmiRC); } catch (const InternalFailure& e) { log("Set sensor failed", entry("SENSOR_NUM=%d", sensorNumber)); commit(); return ipmi::responseUnspecifiedError(); } catch (const std::runtime_error& e) { log(e.what()); return ipmi::responseUnspecifiedError(); } } /** @brief implements the get sensor reading command * @param sensorNum - sensor number * * @returns IPMI completion code plus response data * - senReading - sensor reading * - reserved * - readState - sensor reading state enabled * - senScanState - sensor scan state disabled * - allEventMessageState - all Event message state disabled * - assertionStatesLsb - threshold levels states * - assertionStatesMsb - discrete reading sensor states */ ipmi::RspType ipmiSensorGetSensorReading([[maybe_unused]] ipmi::Context::ptr& ctx, uint8_t sensorNum) { if (sensorNum == 0xFF) { return ipmi::responseInvalidFieldRequest(); } const auto iter = ipmi::sensor::sensors.find(sensorNum); if (iter == ipmi::sensor::sensors.end()) { return ipmi::responseSensorInvalid(); } if (ipmi::sensor::Mutability::Read != (iter->second.mutability & ipmi::sensor::Mutability::Read)) { return ipmi::responseIllegalCommand(); } try { #ifdef FEATURE_SENSORS_CACHE auto& sensorData = sensorCacheMap[sensorNum]; if (!sensorData.has_value()) { // No cached value, try read it std::string service; boost::system::error_code ec; const auto& sensorInfo = iter->second; ec = ipmi::getService(ctx, sensorInfo.sensorInterface, sensorInfo.sensorPath, service); if (ec) { return ipmi::responseUnspecifiedError(); } fillSensorIdServiceMap(sensorInfo.sensorPath, sensorInfo.propertyInterfaces.begin()->first, iter->first, service); ipmi::PropertyMap props; ec = ipmi::getAllDbusProperties( ctx, service, sensorInfo.sensorPath, sensorInfo.propertyInterfaces.begin()->first, props); if (ec) { fprintf(stderr, "Failed to get sensor %s, %d: %s\n", sensorInfo.sensorPath.c_str(), ec.value(), ec.message().c_str()); // Intitilizing with default values constexpr uint8_t senReading = 0; constexpr uint5_t reserved{0}; constexpr bool readState = true; constexpr bool senScanState = false; constexpr bool allEventMessageState = false; constexpr uint8_t assertionStatesLsb = 0; constexpr uint8_t assertionStatesMsb = 0; return ipmi::responseSuccess(senReading, reserved, readState, senScanState, allEventMessageState, assertionStatesLsb, assertionStatesMsb); } sensorInfo.getFunc(sensorNum, sensorInfo, props); } return ipmi::responseSuccess( sensorData->response.reading, uint5_t(0), sensorData->response.readingOrStateUnavailable, sensorData->response.scanningEnabled, sensorData->response.allEventMessagesEnabled, sensorData->response.thresholdLevelsStates, sensorData->response.discreteReadingSensorStates); #else ipmi::sensor::GetSensorResponse getResponse = iter->second.getFunc(iter->second); return ipmi::responseSuccess(getResponse.reading, uint5_t(0), getResponse.readingOrStateUnavailable, getResponse.scanningEnabled, getResponse.allEventMessagesEnabled, getResponse.thresholdLevelsStates, getResponse.discreteReadingSensorStates); #endif } #ifdef UPDATE_FUNCTIONAL_ON_FAIL catch (const SensorFunctionalError& e) { return ipmi::responseResponseError(); } #endif catch (const std::exception& e) { // Intitilizing with default values constexpr uint8_t senReading = 0; constexpr uint5_t reserved{0}; constexpr bool readState = true; constexpr bool senScanState = false; constexpr bool allEventMessageState = false; constexpr uint8_t assertionStatesLsb = 0; constexpr uint8_t assertionStatesMsb = 0; return ipmi::responseSuccess(senReading, reserved, readState, senScanState, allEventMessageState, assertionStatesLsb, assertionStatesMsb); } } get_sdr::GetSensorThresholdsResponse getSensorThresholds(ipmi::Context::ptr& ctx, uint8_t sensorNum) { get_sdr::GetSensorThresholdsResponse resp{}; constexpr auto warningThreshIntf = "xyz.openbmc_project.Sensor.Threshold.Warning"; constexpr auto criticalThreshIntf = "xyz.openbmc_project.Sensor.Threshold.Critical"; const auto iter = ipmi::sensor::sensors.find(sensorNum); const auto info = iter->second; std::string service; boost::system::error_code ec; ec = ipmi::getService(ctx, info.sensorInterface, info.sensorPath, service); if (ec) { return resp; } ipmi::PropertyMap warnThresholds; ec = ipmi::getAllDbusProperties(ctx, service, info.sensorPath, warningThreshIntf, warnThresholds); int32_t minClamp; int32_t maxClamp; int32_t rawData; constexpr uint8_t sensorUnitsSignedBits = 2 << 6; constexpr uint8_t signedDataFormat = 0x80; if ((info.sensorUnits1 & sensorUnitsSignedBits) == signedDataFormat) { minClamp = std::numeric_limits::lowest(); maxClamp = std::numeric_limits::max(); } else { minClamp = std::numeric_limits::lowest(); maxClamp = std::numeric_limits::max(); } if (!ec) { double warnLow = ipmi::mappedVariant( warnThresholds, "WarningLow", std::numeric_limits::quiet_NaN()); double warnHigh = ipmi::mappedVariant( warnThresholds, "WarningHigh", std::numeric_limits::quiet_NaN()); if (std::isfinite(warnLow)) { warnLow *= std::pow(10, info.scale - info.exponentR); rawData = round((warnLow - info.scaledOffset) / info.coefficientM); resp.lowerNonCritical = static_cast(std::clamp(rawData, minClamp, maxClamp)); resp.validMask |= static_cast( ipmi::sensor::ThresholdMask::NON_CRITICAL_LOW_MASK); } if (std::isfinite(warnHigh)) { warnHigh *= std::pow(10, info.scale - info.exponentR); rawData = round((warnHigh - info.scaledOffset) / info.coefficientM); resp.upperNonCritical = static_cast(std::clamp(rawData, minClamp, maxClamp)); resp.validMask |= static_cast( ipmi::sensor::ThresholdMask::NON_CRITICAL_HIGH_MASK); } } ipmi::PropertyMap critThresholds; ec = ipmi::getAllDbusProperties(ctx, service, info.sensorPath, criticalThreshIntf, critThresholds); if (!ec) { double critLow = ipmi::mappedVariant( critThresholds, "CriticalLow", std::numeric_limits::quiet_NaN()); double critHigh = ipmi::mappedVariant( critThresholds, "CriticalHigh", std::numeric_limits::quiet_NaN()); if (std::isfinite(critLow)) { critLow *= std::pow(10, info.scale - info.exponentR); rawData = round((critLow - info.scaledOffset) / info.coefficientM); resp.lowerCritical = static_cast(std::clamp(rawData, minClamp, maxClamp)); resp.validMask |= static_cast( ipmi::sensor::ThresholdMask::CRITICAL_LOW_MASK); } if (std::isfinite(critHigh)) { critHigh *= std::pow(10, info.scale - info.exponentR); rawData = round((critHigh - info.scaledOffset) / info.coefficientM); resp.upperCritical = static_cast(std::clamp(rawData, minClamp, maxClamp)); resp.validMask |= static_cast( ipmi::sensor::ThresholdMask::CRITICAL_HIGH_MASK); } } return resp; } /** @brief implements the get sensor thresholds command * @param ctx - IPMI context pointer * @param sensorNum - sensor number * * @returns IPMI completion code plus response data * - validMask - threshold mask * - lower non-critical threshold - IPMI messaging state * - lower critical threshold - link authentication state * - lower non-recoverable threshold - callback state * - upper non-critical threshold * - upper critical * - upper non-recoverable */ ipmi::RspType ipmiSensorGetSensorThresholds(ipmi::Context::ptr& ctx, uint8_t sensorNum) { constexpr auto valueInterface = "xyz.openbmc_project.Sensor.Value"; const auto iter = ipmi::sensor::sensors.find(sensorNum); if (iter == ipmi::sensor::sensors.end()) { return ipmi::responseSensorInvalid(); } const auto info = iter->second; // Proceed only if the sensor value interface is implemented. if (info.propertyInterfaces.find(valueInterface) == info.propertyInterfaces.end()) { // return with valid mask as 0 return ipmi::responseSuccess(); } auto it = sensorThresholdMap.find(sensorNum); if (it == sensorThresholdMap.end()) { sensorThresholdMap[sensorNum] = getSensorThresholds(ctx, sensorNum); } const auto& resp = sensorThresholdMap[sensorNum]; return ipmi::responseSuccess(resp.validMask, resp.lowerNonCritical, resp.lowerCritical, resp.lowerNonRecoverable, resp.upperNonCritical, resp.upperCritical, resp.upperNonRecoverable); } /** @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( ipmi::Context::ptr& ctx, 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, uint8_t upperNonCritical, uint8_t upperCritical, uint8_t) { 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(); } constexpr auto valueInterface = "xyz.openbmc_project.Sensor.Value"; const auto iter = ipmi::sensor::sensors.find(sensorNum); if (iter == ipmi::sensor::sensors.end()) { return ipmi::responseSensorInvalid(); } const auto& info = iter->second; // Proceed only if the sensor value interface is implemented. if (info.propertyInterfaces.find(valueInterface) == info.propertyInterfaces.end()) { // return with valid mask as 0 return ipmi::responseSuccess(); } constexpr auto warningThreshIntf = "xyz.openbmc_project.Sensor.Threshold.Warning"; constexpr auto criticalThreshIntf = "xyz.openbmc_project.Sensor.Threshold.Critical"; std::string service; boost::system::error_code ec; ec = ipmi::getService(ctx, info.sensorInterface, info.sensorPath, service); if (ec) { 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) { ipmi::PropertyMap findThreshold; ec = ipmi::getAllDbusProperties(ctx, service, info.sensorPath, criticalThreshIntf, findThreshold); if (!ec) { if (lowerCriticalThreshMask) { auto findLower = findThreshold.find("CriticalLow"); if (findLower == findThreshold.end()) { return ipmi::responseInvalidFieldRequest(); } thresholdsToSet.emplace_back("CriticalLow", lowerCritical, criticalThreshIntf); } if (upperCriticalThreshMask) { auto findUpper = findThreshold.find("CriticalHigh"); if (findUpper == findThreshold.end()) { return ipmi::responseInvalidFieldRequest(); } thresholdsToSet.emplace_back("CriticalHigh", upperCritical, criticalThreshIntf); } } } if (lowerNonCriticalThreshMask || upperNonCriticalThreshMask) { ipmi::PropertyMap findThreshold; ec = ipmi::getAllDbusProperties(ctx, service, info.sensorPath, warningThreshIntf, findThreshold); if (!ec) { if (lowerNonCriticalThreshMask) { auto findLower = findThreshold.find("WarningLow"); if (findLower == findThreshold.end()) { return ipmi::responseInvalidFieldRequest(); } thresholdsToSet.emplace_back("WarningLow", lowerNonCritical, warningThreshIntf); } if (upperNonCriticalThreshMask) { auto findUpper = findThreshold.find("WarningHigh"); if (findUpper == findThreshold.end()) { return ipmi::responseInvalidFieldRequest(); } thresholdsToSet.emplace_back("WarningHigh", upperNonCritical, warningThreshIntf); } } } for (const auto& property : thresholdsToSet) { // from section 36.3 in the IPMI Spec, assume all linear double valueToSet = ((info.coefficientM * std::get(property)) + (info.scaledOffset * std::pow(10.0, info.scale))) * std::pow(10.0, info.exponentR); ipmi::setDbusProperty( ctx, service, info.sensorPath, std::get(property), std::get(property), ipmi::Value(valueToSet)); } // Invalidate the cache sensorThresholdMap.erase(sensorNum); return ipmi::responseSuccess(); } /** @brief implements the get SDR Info command * @param count - Operation * * @returns IPMI completion code plus response data * - sdrCount - sensor/SDR count * - lunsAndDynamicPopulation - static/Dynamic sensor population flag */ ipmi::RspType ipmiSensorGetDeviceSdrInfo(std::optional count) { uint8_t sdrCount; // multiple LUNs not supported. constexpr uint8_t lunsAndDynamicPopulation = 1; constexpr uint8_t getSdrCount = 0x01; constexpr uint8_t getSensorCount = 0x00; if (count.value_or(0) == getSdrCount) { // Get SDR count. This returns the total number of SDRs in the device. const auto& entityRecords = ipmi::sensor::EntityInfoMapContainer::getContainer() ->getIpmiEntityRecords(); sdrCount = ipmi::sensor::sensors.size() + frus.size() + entityRecords.size(); } else if (count.value_or(0) == getSensorCount) { // Get Sensor count. This returns the number of sensors sdrCount = ipmi::sensor::sensors.size(); } else { return ipmi::responseInvalidCommandOnLun(); } return ipmi::responseSuccess(sdrCount, lunsAndDynamicPopulation); } /** @brief implements the reserve SDR command * @returns IPMI completion code plus response data * - reservationID - reservation ID */ ipmi::RspType ipmiSensorReserveSdr() { // A constant reservation ID is okay until we implement add/remove SDR. constexpr uint16_t reservationID = 1; return ipmi::responseSuccess(reservationID); } void setUnitFieldsForObject(const ipmi::sensor::Info* info, get_sdr::SensorDataFullRecordBody* body) { namespace server = sdbusplus::xyz::openbmc_project::Sensor::server; try { auto unit = server::Value::convertUnitFromString(info->unit); // Unit strings defined in // phosphor-dbus-interfaces/xyz/openbmc_project/Sensor/Value.interface.yaml switch (unit) { case server::Value::Unit::DegreesC: body->sensor_units_2_base = get_sdr::SENSOR_UNIT_DEGREES_C; break; case server::Value::Unit::RPMS: body->sensor_units_2_base = get_sdr::SENSOR_UNIT_RPM; break; case server::Value::Unit::Volts: body->sensor_units_2_base = get_sdr::SENSOR_UNIT_VOLTS; break; case server::Value::Unit::Meters: body->sensor_units_2_base = get_sdr::SENSOR_UNIT_METERS; break; case server::Value::Unit::Amperes: body->sensor_units_2_base = get_sdr::SENSOR_UNIT_AMPERES; break; case server::Value::Unit::Joules: body->sensor_units_2_base = get_sdr::SENSOR_UNIT_JOULES; break; case server::Value::Unit::Watts: body->sensor_units_2_base = get_sdr::SENSOR_UNIT_WATTS; break; default: // Cannot be hit. std::fprintf(stderr, "Unknown value unit type: = %s\n", info->unit.c_str()); } } catch (const sdbusplus::exception::InvalidEnumString& e) { log("Warning: no unit provided for sensor!"); } } ipmi_ret_t populate_record_from_dbus(get_sdr::SensorDataFullRecordBody* body, const ipmi::sensor::Info* info, ipmi_data_len_t) { /* Functional sensor case */ if (isAnalogSensor(info->propertyInterfaces.begin()->first)) { body->sensor_units_1 = info->sensorUnits1; // default is 0. unsigned, no // rate, no modifier, not a % /* Unit info */ setUnitFieldsForObject(info, body); get_sdr::body::set_b(info->coefficientB, body); get_sdr::body::set_m(info->coefficientM, body); get_sdr::body::set_b_exp(info->exponentB, body); get_sdr::body::set_r_exp(info->exponentR, body); get_sdr::body::set_id_type(0b00, body); // 00 = unicode } /* ID string */ auto id_string = info->sensorName; if (id_string.empty()) { id_string = info->sensorNameFunc(*info); } if (id_string.length() > FULL_RECORD_ID_STR_MAX_LENGTH) { get_sdr::body::set_id_strlen(FULL_RECORD_ID_STR_MAX_LENGTH, body); } else { get_sdr::body::set_id_strlen(id_string.length(), body); } strncpy(body->id_string, id_string.c_str(), get_sdr::body::get_id_strlen(body)); return IPMI_CC_OK; }; ipmi_ret_t ipmi_fru_get_sdr(ipmi_request_t request, ipmi_response_t response, ipmi_data_len_t data_len) { auto req = reinterpret_cast(request); auto resp = reinterpret_cast(response); get_sdr::SensorDataFruRecord record{}; auto dataLength = 0; auto fru = frus.begin(); uint8_t fruID{}; auto recordID = get_sdr::request::get_record_id(req); fruID = recordID - FRU_RECORD_ID_START; fru = frus.find(fruID); if (fru == frus.end()) { return IPMI_CC_SENSOR_INVALID; } /* Header */ get_sdr::header::set_record_id(recordID, &(record.header)); record.header.sdr_version = SDR_VERSION; // Based on IPMI Spec v2.0 rev 1.1 record.header.record_type = get_sdr::SENSOR_DATA_FRU_RECORD; record.header.record_length = sizeof(record.key) + sizeof(record.body); /* Key */ record.key.fruID = fruID; record.key.accessLun |= IPMI_LOGICAL_FRU; record.key.deviceAddress = BMCSlaveAddress; /* Body */ record.body.entityID = fru->second[0].entityID; record.body.entityInstance = fru->second[0].entityInstance; record.body.deviceType = fruInventoryDevice; record.body.deviceTypeModifier = IPMIFruInventory; /* Device ID string */ auto deviceID = fru->second[0].path.substr(fru->second[0].path.find_last_of('/') + 1, fru->second[0].path.length()); if (deviceID.length() > get_sdr::FRU_RECORD_DEVICE_ID_MAX_LENGTH) { get_sdr::body::set_device_id_strlen( get_sdr::FRU_RECORD_DEVICE_ID_MAX_LENGTH, &(record.body)); } else { get_sdr::body::set_device_id_strlen(deviceID.length(), &(record.body)); } strncpy(record.body.deviceID, deviceID.c_str(), get_sdr::body::get_device_id_strlen(&(record.body))); if (++fru == frus.end()) { // we have reached till end of fru, so assign the next record id to // 512(Max fru ID = 511) + Entity Record ID(may start with 0). const auto& entityRecords = ipmi::sensor::EntityInfoMapContainer::getContainer() ->getIpmiEntityRecords(); auto next_record_id = (entityRecords.size()) ? entityRecords.begin()->first + ENTITY_RECORD_ID_START : END_OF_RECORD; get_sdr::response::set_next_record_id(next_record_id, resp); } else { get_sdr::response::set_next_record_id( (FRU_RECORD_ID_START + fru->first), resp); } // Check for invalid offset size if (req->offset > sizeof(record)) { return IPMI_CC_PARM_OUT_OF_RANGE; } dataLength = std::min(static_cast(req->bytes_to_read), sizeof(record) - req->offset); std::memcpy(resp->record_data, reinterpret_cast(&record) + req->offset, dataLength); *data_len = dataLength; *data_len += 2; // additional 2 bytes for next record ID return IPMI_CC_OK; } ipmi_ret_t ipmi_entity_get_sdr(ipmi_request_t request, ipmi_response_t response, ipmi_data_len_t data_len) { auto req = reinterpret_cast(request); auto resp = reinterpret_cast(response); get_sdr::SensorDataEntityRecord record{}; auto dataLength = 0; const auto& entityRecords = ipmi::sensor::EntityInfoMapContainer::getContainer() ->getIpmiEntityRecords(); auto entity = entityRecords.begin(); uint8_t entityRecordID; auto recordID = get_sdr::request::get_record_id(req); entityRecordID = recordID - ENTITY_RECORD_ID_START; entity = entityRecords.find(entityRecordID); if (entity == entityRecords.end()) { return IPMI_CC_SENSOR_INVALID; } /* Header */ get_sdr::header::set_record_id(recordID, &(record.header)); record.header.sdr_version = SDR_VERSION; // Based on IPMI Spec v2.0 rev 1.1 record.header.record_type = get_sdr::SENSOR_DATA_ENTITY_RECORD; record.header.record_length = sizeof(record.key) + sizeof(record.body); /* Key */ record.key.containerEntityId = entity->second.containerEntityId; record.key.containerEntityInstance = entity->second.containerEntityInstance; get_sdr::key::set_flags(entity->second.isList, entity->second.isLinked, &(record.key)); record.key.entityId1 = entity->second.containedEntities[0].first; record.key.entityInstance1 = entity->second.containedEntities[0].second; /* Body */ record.body.entityId2 = entity->second.containedEntities[1].first; record.body.entityInstance2 = entity->second.containedEntities[1].second; record.body.entityId3 = entity->second.containedEntities[2].first; record.body.entityInstance3 = entity->second.containedEntities[2].second; record.body.entityId4 = entity->second.containedEntities[3].first; record.body.entityInstance4 = entity->second.containedEntities[3].second; if (++entity == entityRecords.end()) { get_sdr::response::set_next_record_id(END_OF_RECORD, resp); // last record } else { get_sdr::response::set_next_record_id( (ENTITY_RECORD_ID_START + entity->first), resp); } // Check for invalid offset size if (req->offset > sizeof(record)) { return IPMI_CC_PARM_OUT_OF_RANGE; } dataLength = std::min(static_cast(req->bytes_to_read), sizeof(record) - req->offset); std::memcpy(resp->record_data, reinterpret_cast(&record) + req->offset, dataLength); *data_len = dataLength; *data_len += 2; // additional 2 bytes for next record ID return IPMI_CC_OK; } ipmi_ret_t ipmi_sen_get_sdr(ipmi_netfn_t, ipmi_cmd_t, ipmi_request_t request, ipmi_response_t response, ipmi_data_len_t data_len, ipmi_context_t) { ipmi_ret_t ret = IPMI_CC_OK; get_sdr::GetSdrReq* req = (get_sdr::GetSdrReq*)request; get_sdr::GetSdrResp* resp = (get_sdr::GetSdrResp*)response; // Note: we use an iterator so we can provide the next ID at the end of // the call. auto sensor = ipmi::sensor::sensors.begin(); auto recordID = get_sdr::request::get_record_id(req); // At the beginning of a scan, the host side will send us id=0. if (recordID != 0) { // recordID 0 to 255 means it is a FULL record. // recordID 256 to 511 means it is a FRU record. // recordID greater then 511 means it is a Entity Association // record. Currently we are supporting three record types: FULL // record, FRU record and Enttiy Association record. if (recordID >= ENTITY_RECORD_ID_START) { return ipmi_entity_get_sdr(request, response, data_len); } else if (recordID >= FRU_RECORD_ID_START && recordID < ENTITY_RECORD_ID_START) { return ipmi_fru_get_sdr(request, response, data_len); } else { sensor = ipmi::sensor::sensors.find(recordID); if (sensor == ipmi::sensor::sensors.end()) { return IPMI_CC_SENSOR_INVALID; } } } uint8_t sensor_id = sensor->first; auto it = sdrCacheMap.find(sensor_id); if (it == sdrCacheMap.end()) { /* Header */ get_sdr::SensorDataFullRecord record = {}; get_sdr::header::set_record_id(sensor_id, &(record.header)); record.header.sdr_version = 0x51; // Based on IPMI Spec v2.0 rev 1.1 record.header.record_type = get_sdr::SENSOR_DATA_FULL_RECORD; record.header.record_length = sizeof(record.key) + sizeof(record.body); /* Key */ get_sdr::key::set_owner_id_bmc(&(record.key)); record.key.sensor_number = sensor_id; /* Body */ record.body.entity_id = sensor->second.entityType; record.body.sensor_type = sensor->second.sensorType; record.body.event_reading_type = sensor->second.sensorReadingType; record.body.entity_instance = sensor->second.instance; if (ipmi::sensor::Mutability::Write == (sensor->second.mutability & ipmi::sensor::Mutability::Write)) { get_sdr::body::init_settable_state(true, &(record.body)); } // Set the type-specific details given the DBus interface populate_record_from_dbus(&(record.body), &(sensor->second), data_len); sdrCacheMap[sensor_id] = std::move(record); } const auto& record = sdrCacheMap[sensor_id]; if (++sensor == ipmi::sensor::sensors.end()) { // we have reached till end of sensor, so assign the next record id // to 256(Max Sensor ID = 255) + FRU ID(may start with 0). auto next_record_id = (frus.size()) ? frus.begin()->first + FRU_RECORD_ID_START : END_OF_RECORD; get_sdr::response::set_next_record_id(next_record_id, resp); } else { get_sdr::response::set_next_record_id(sensor->first, resp); } if (req->offset > sizeof(record)) { return IPMI_CC_PARM_OUT_OF_RANGE; } // data_len will ultimately be the size of the record, plus // the size of the next record ID: *data_len = std::min(static_cast(req->bytes_to_read), sizeof(record) - req->offset); std::memcpy(resp->record_data, reinterpret_cast(&record) + req->offset, *data_len); // data_len should include the LSB and MSB: *data_len += sizeof(resp->next_record_id_lsb) + sizeof(resp->next_record_id_msb); return ret; } static bool isFromSystemChannel() { // TODO we could not figure out where the request is from based on IPMI // command handler parameters. because of it, we can not differentiate // request from SMS/SMM or IPMB channel return true; } ipmi_ret_t ipmicmdPlatformEvent(ipmi_netfn_t, ipmi_cmd_t, ipmi_request_t request, ipmi_response_t, ipmi_data_len_t dataLen, ipmi_context_t) { uint16_t generatorID; size_t count; bool assert = true; std::string sensorPath; size_t paraLen = *dataLen; PlatformEventRequest* req; *dataLen = 0; if ((paraLen < selSystemEventSizeWith1Bytes) || (paraLen > selSystemEventSizeWith3Bytes)) { return IPMI_CC_REQ_DATA_LEN_INVALID; } if (isFromSystemChannel()) { // first byte for SYSTEM Interface is Generator ID // +1 to get common struct req = reinterpret_cast((uint8_t*)request + 1); // Capture the generator ID generatorID = *reinterpret_cast(request); // Platform Event usually comes from other firmware, like BIOS. // Unlike BMC sensor, it does not have BMC DBUS sensor path. sensorPath = "System"; } else { req = reinterpret_cast(request); // TODO GenratorID for IPMB is combination of RqSA and RqLUN generatorID = 0xff; sensorPath = "IPMB"; } // Content of event data field depends on sensor class. // When data0 bit[5:4] is non-zero, valid data counts is 3. // When data0 bit[7:6] is non-zero, valid data counts is 2. if (((req->data[0] & byte3EnableMask) != 0 && paraLen < selSystemEventSizeWith3Bytes) || ((req->data[0] & byte2EnableMask) != 0 && paraLen < selSystemEventSizeWith2Bytes)) { return IPMI_CC_REQ_DATA_LEN_INVALID; } // Count bytes of Event Data if ((req->data[0] & byte3EnableMask) != 0) { count = 3; } else if ((req->data[0] & byte2EnableMask) != 0) { count = 2; } else { count = 1; } assert = req->eventDirectionType & directionMask ? false : true; std::vector eventData(req->data, req->data + count); sdbusplus::bus_t dbus(bus); std::string service = ipmi::getService(dbus, ipmiSELAddInterface, ipmiSELPath); sdbusplus::message_t writeSEL = dbus.new_method_call( service.c_str(), ipmiSELPath, ipmiSELAddInterface, "IpmiSelAdd"); writeSEL.append(ipmiSELAddMessage, sensorPath, eventData, assert, generatorID); try { dbus.call(writeSEL); } catch (const sdbusplus::exception_t& e) { phosphor::logging::log(e.what()); return IPMI_CC_UNSPECIFIED_ERROR; } return IPMI_CC_OK; } void register_netfn_sen_functions() { // Handlers with dbus-sdr handler implementation. // Do not register the hander if it dynamic sensors stack is used. #ifndef FEATURE_DYNAMIC_SENSORS #ifdef FEATURE_SENSORS_CACHE // Initialize the sensor matches initSensorMatches(); #endif // ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnSensor, ipmi::sensor_event::cmdSetSensorReadingAndEvtSts, ipmi::Privilege::Operator, ipmiSetSensorReading); // ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnSensor, ipmi::sensor_event::cmdGetSensorReading, ipmi::Privilege::User, ipmiSensorGetSensorReading); // ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnSensor, ipmi::sensor_event::cmdReserveDeviceSdrRepository, ipmi::Privilege::User, ipmiSensorReserveSdr); // ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnSensor, ipmi::sensor_event::cmdGetDeviceSdrInfo, ipmi::Privilege::User, ipmiSensorGetDeviceSdrInfo); // ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnSensor, ipmi::sensor_event::cmdGetSensorThreshold, ipmi::Privilege::User, ipmiSensorGetSensorThresholds); // ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnSensor, ipmi::sensor_event::cmdSetSensorThreshold, ipmi::Privilege::User, ipmiSenSetSensorThresholds); // ipmi_register_callback(NETFUN_SENSOR, IPMI_CMD_GET_DEVICE_SDR, nullptr, ipmi_sen_get_sdr, PRIVILEGE_USER); #endif // Common Handers used by both implementation. // ipmi_register_callback(NETFUN_SENSOR, IPMI_CMD_PLATFORM_EVENT, nullptr, ipmicmdPlatformEvent, PRIVILEGE_OPERATOR); // ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnSensor, ipmi::sensor_event::cmdGetSensorType, ipmi::Privilege::User, ipmiGetSensorType); return; }