1 /* 2 // Copyright (c) 2017 2018 Intel Corporation 3 // 4 // Licensed under the Apache License, Version 2.0 (the "License"); 5 // you may not use this file except in compliance with the License. 6 // You may obtain a copy of the License at 7 // 8 // http://www.apache.org/licenses/LICENSE-2.0 9 // 10 // Unless required by applicable law or agreed to in writing, software 11 // distributed under the License is distributed on an "AS IS" BASIS, 12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 // See the License for the specific language governing permissions and 14 // limitations under the License. 15 */ 16 17 #include "sensorcommands.hpp" 18 19 #include "commandutils.hpp" 20 #include "ipmi_to_redfish_hooks.hpp" 21 #include "sdrutils.hpp" 22 #include "sensorutils.hpp" 23 #include "storagecommands.hpp" 24 #include "types.hpp" 25 26 #include <boost/algorithm/string.hpp> 27 #include <boost/container/flat_map.hpp> 28 #include <ipmid/api.hpp> 29 #include <ipmid/utils.hpp> 30 #include <phosphor-logging/log.hpp> 31 #include <sdbusplus/bus.hpp> 32 33 #include <algorithm> 34 #include <array> 35 #include <chrono> 36 #include <cmath> 37 #include <cstring> 38 #include <iostream> 39 #include <map> 40 #include <memory> 41 #include <optional> 42 #include <stdexcept> 43 #include <string> 44 #include <utility> 45 #include <variant> 46 47 namespace ipmi 48 { 49 using ManagedObjectType = 50 std::map<sdbusplus::message::object_path, 51 std::map<std::string, std::map<std::string, DbusVariant>>>; 52 53 static constexpr int sensorMapUpdatePeriod = 10; 54 static constexpr int sensorMapSdrUpdatePeriod = 60; 55 56 constexpr size_t maxSDRTotalSize = 57 76; // Largest SDR Record Size (type 01) + SDR Overheader Size 58 constexpr static const uint32_t noTimestamp = 0xFFFFFFFF; 59 60 static uint16_t sdrReservationID; 61 static uint32_t sdrLastAdd = noTimestamp; 62 static uint32_t sdrLastRemove = noTimestamp; 63 static constexpr size_t lastRecordIndex = 0xFFFF; 64 65 // The IPMI spec defines four Logical Units (LUN), each capable of supporting 66 // 255 sensors. The 256 values assigned to LUN 2 are special and are not used 67 // for general purpose sensors. Each LUN reserves location 0xFF. The maximum 68 // number of IPMI sensors are LUN 0 + LUN 1 + LUN 2, less the reserved 69 // location. 70 static constexpr size_t maxIPMISensors = ((3 * 256) - (3 * 1)); 71 72 static constexpr size_t lun0MaxSensorNum = 0xfe; 73 static constexpr size_t lun1MaxSensorNum = 0x1fe; 74 static constexpr size_t lun3MaxSensorNum = 0x3fe; 75 static constexpr int GENERAL_ERROR = -1; 76 77 SensorSubTree sensorTree; 78 79 static boost::container::flat_map<std::string, ManagedObjectType> SensorCache; 80 81 // Specify the comparison required to sort and find char* map objects 82 struct CmpStr 83 { 84 bool operator()(const char* a, const char* b) const 85 { 86 return std::strcmp(a, b) < 0; 87 } 88 }; 89 const static boost::container::flat_map<const char*, SensorUnits, CmpStr> 90 sensorUnits{{{"temperature", SensorUnits::degreesC}, 91 {"voltage", SensorUnits::volts}, 92 {"current", SensorUnits::amps}, 93 {"fan_tach", SensorUnits::rpm}, 94 {"power", SensorUnits::watts}}}; 95 96 void registerSensorFunctions() __attribute__((constructor)); 97 98 static sdbusplus::bus::match::match sensorAdded( 99 *getSdBus(), 100 "type='signal',member='InterfacesAdded',arg0path='/xyz/openbmc_project/" 101 "sensors/'", 102 [](sdbusplus::message::message& m) { 103 sensorTree.clear(); 104 sdrLastAdd = std::chrono::duration_cast<std::chrono::seconds>( 105 std::chrono::system_clock::now().time_since_epoch()) 106 .count(); 107 }); 108 109 static sdbusplus::bus::match::match sensorRemoved( 110 *getSdBus(), 111 "type='signal',member='InterfacesRemoved',arg0path='/xyz/openbmc_project/" 112 "sensors/'", 113 [](sdbusplus::message::message& m) { 114 sensorTree.clear(); 115 sdrLastRemove = std::chrono::duration_cast<std::chrono::seconds>( 116 std::chrono::system_clock::now().time_since_epoch()) 117 .count(); 118 }); 119 120 // this keeps track of deassertions for sensor event status command. A 121 // deasertion can only happen if an assertion was seen first. 122 static boost::container::flat_map< 123 std::string, boost::container::flat_map<std::string, std::optional<bool>>> 124 thresholdDeassertMap; 125 126 static sdbusplus::bus::match::match thresholdChanged( 127 *getSdBus(), 128 "type='signal',member='PropertiesChanged',interface='org.freedesktop.DBus." 129 "Properties',arg0namespace='xyz.openbmc_project.Sensor.Threshold'", 130 [](sdbusplus::message::message& m) { 131 boost::container::flat_map<std::string, std::variant<bool, double>> 132 values; 133 m.read(std::string(), values); 134 135 auto findAssert = 136 std::find_if(values.begin(), values.end(), [](const auto& pair) { 137 return pair.first.find("Alarm") != std::string::npos; 138 }); 139 if (findAssert != values.end()) 140 { 141 auto ptr = std::get_if<bool>(&(findAssert->second)); 142 if (ptr == nullptr) 143 { 144 phosphor::logging::log<phosphor::logging::level::ERR>( 145 "thresholdChanged: Assert non bool"); 146 return; 147 } 148 if (*ptr) 149 { 150 phosphor::logging::log<phosphor::logging::level::INFO>( 151 "thresholdChanged: Assert", 152 phosphor::logging::entry("SENSOR=%s", m.get_path())); 153 thresholdDeassertMap[m.get_path()][findAssert->first] = *ptr; 154 } 155 else 156 { 157 auto& value = 158 thresholdDeassertMap[m.get_path()][findAssert->first]; 159 if (value) 160 { 161 phosphor::logging::log<phosphor::logging::level::INFO>( 162 "thresholdChanged: deassert", 163 phosphor::logging::entry("SENSOR=%s", m.get_path())); 164 value = *ptr; 165 } 166 } 167 } 168 }); 169 170 static void getSensorMaxMin(const SensorMap& sensorMap, double& max, 171 double& min) 172 { 173 max = 127; 174 min = -128; 175 176 auto sensorObject = sensorMap.find("xyz.openbmc_project.Sensor.Value"); 177 auto critical = 178 sensorMap.find("xyz.openbmc_project.Sensor.Threshold.Critical"); 179 auto warning = 180 sensorMap.find("xyz.openbmc_project.Sensor.Threshold.Warning"); 181 182 if (sensorObject != sensorMap.end()) 183 { 184 auto maxMap = sensorObject->second.find("MaxValue"); 185 auto minMap = sensorObject->second.find("MinValue"); 186 187 if (maxMap != sensorObject->second.end()) 188 { 189 max = std::visit(VariantToDoubleVisitor(), maxMap->second); 190 } 191 if (minMap != sensorObject->second.end()) 192 { 193 min = std::visit(VariantToDoubleVisitor(), minMap->second); 194 } 195 } 196 if (critical != sensorMap.end()) 197 { 198 auto lower = critical->second.find("CriticalLow"); 199 auto upper = critical->second.find("CriticalHigh"); 200 if (lower != critical->second.end()) 201 { 202 double value = std::visit(VariantToDoubleVisitor(), lower->second); 203 min = std::min(value, min); 204 } 205 if (upper != critical->second.end()) 206 { 207 double value = std::visit(VariantToDoubleVisitor(), upper->second); 208 max = std::max(value, max); 209 } 210 } 211 if (warning != sensorMap.end()) 212 { 213 214 auto lower = warning->second.find("WarningLow"); 215 auto upper = warning->second.find("WarningHigh"); 216 if (lower != warning->second.end()) 217 { 218 double value = std::visit(VariantToDoubleVisitor(), lower->second); 219 min = std::min(value, min); 220 } 221 if (upper != warning->second.end()) 222 { 223 double value = std::visit(VariantToDoubleVisitor(), upper->second); 224 max = std::max(value, max); 225 } 226 } 227 } 228 229 static bool getSensorMap(boost::asio::yield_context yield, 230 std::string sensorConnection, std::string sensorPath, 231 SensorMap& sensorMap, 232 int updatePeriod = sensorMapUpdatePeriod) 233 { 234 static boost::container::flat_map< 235 std::string, std::chrono::time_point<std::chrono::steady_clock>> 236 updateTimeMap; 237 238 auto updateFind = updateTimeMap.find(sensorConnection); 239 auto lastUpdate = std::chrono::time_point<std::chrono::steady_clock>(); 240 if (updateFind != updateTimeMap.end()) 241 { 242 lastUpdate = updateFind->second; 243 } 244 245 auto now = std::chrono::steady_clock::now(); 246 247 if (std::chrono::duration_cast<std::chrono::seconds>(now - lastUpdate) 248 .count() > updatePeriod) 249 { 250 std::shared_ptr<sdbusplus::asio::connection> dbus = getSdBus(); 251 boost::system::error_code ec; 252 auto managedObjects = dbus->yield_method_call<ManagedObjectType>( 253 yield, ec, sensorConnection.c_str(), "/", 254 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 255 if (ec) 256 { 257 phosphor::logging::log<phosphor::logging::level::ERR>( 258 "GetMangagedObjects for getSensorMap failed", 259 phosphor::logging::entry("ERROR=%s", ec.message().c_str())); 260 261 return false; 262 } 263 264 SensorCache[sensorConnection] = managedObjects; 265 // Update time after finish building the map which allow the 266 // data to be cached for updatePeriod plus the build time. 267 updateTimeMap[sensorConnection] = std::chrono::steady_clock::now(); 268 } 269 auto connection = SensorCache.find(sensorConnection); 270 if (connection == SensorCache.end()) 271 { 272 return false; 273 } 274 auto path = connection->second.find(sensorPath); 275 if (path == connection->second.end()) 276 { 277 return false; 278 } 279 sensorMap = path->second; 280 281 return true; 282 } 283 284 /* sensor commands */ 285 namespace meHealth 286 { 287 constexpr const char* busname = "xyz.openbmc_project.NodeManagerProxy"; 288 constexpr const char* path = "/xyz/openbmc_project/status/me"; 289 constexpr const char* interface = "xyz.openbmc_project.SetHealth"; 290 constexpr const char* method = "SetHealth"; 291 constexpr const char* critical = "critical"; 292 constexpr const char* warning = "warning"; 293 constexpr const char* ok = "ok"; 294 } // namespace meHealth 295 296 static void setMeStatus(uint8_t eventData2, uint8_t eventData3, bool disable) 297 { 298 constexpr const std::array<uint8_t, 10> critical = { 299 0x1, 0x2, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xD, 0xE}; 300 constexpr const std::array<uint8_t, 5> warning = {0x3, 0xA, 0x13, 0x19, 301 0x1A}; 302 303 std::string state; 304 if (std::find(critical.begin(), critical.end(), eventData2) != 305 critical.end()) 306 { 307 state = meHealth::critical; 308 } 309 // special case 0x3 as we only care about a few states 310 else if (eventData2 == 0x3) 311 { 312 if (eventData3 <= 0x2) 313 { 314 state = meHealth::warning; 315 } 316 else 317 { 318 return; 319 } 320 } 321 else if (std::find(warning.begin(), warning.end(), eventData2) != 322 warning.end()) 323 { 324 state = meHealth::warning; 325 } 326 else 327 { 328 return; 329 } 330 if (disable) 331 { 332 state = meHealth::ok; 333 } 334 335 std::shared_ptr<sdbusplus::asio::connection> dbus = getSdBus(); 336 auto setHealth = 337 dbus->new_method_call(meHealth::busname, meHealth::path, 338 meHealth::interface, meHealth::method); 339 setHealth.append(std::to_string(static_cast<size_t>(eventData2)), state); 340 try 341 { 342 dbus->call(setHealth); 343 } 344 catch (const sdbusplus::exception_t&) 345 { 346 phosphor::logging::log<phosphor::logging::level::ERR>( 347 "Failed to set ME Health"); 348 } 349 } 350 351 ipmi::RspType<> ipmiSenPlatformEvent(ipmi::Context::ptr ctx, 352 ipmi::message::Payload& p) 353 { 354 constexpr const uint8_t meId = 0x2C; 355 constexpr const uint8_t meSensorNum = 0x17; 356 constexpr const uint8_t disabled = 0x80; 357 358 uint8_t sysgeneratorID = 0; 359 uint8_t evmRev = 0; 360 uint8_t sensorType = 0; 361 uint8_t sensorNum = 0; 362 uint8_t eventType = 0; 363 uint8_t eventData1 = 0; 364 std::optional<uint8_t> eventData2 = 0; 365 std::optional<uint8_t> eventData3 = 0; 366 uint16_t generatorID = 0; 367 ipmi::ChannelInfo chInfo; 368 369 if (ipmi::getChannelInfo(ctx->channel, chInfo) != ipmi::ccSuccess) 370 { 371 phosphor::logging::log<phosphor::logging::level::ERR>( 372 "Failed to get Channel Info", 373 phosphor::logging::entry("CHANNEL=%d", ctx->channel)); 374 return ipmi::responseUnspecifiedError(); 375 } 376 377 if (static_cast<ipmi::EChannelMediumType>(chInfo.mediumType) == 378 ipmi::EChannelMediumType::systemInterface) 379 { 380 381 p.unpack(sysgeneratorID, evmRev, sensorType, sensorNum, eventType, 382 eventData1, eventData2, eventData3); 383 // Refer to IPMI Spec Table 32: SEL Event Records 384 generatorID = (ctx->channel << 12) // Channel 385 | (0x0 << 10) // Reserved 386 | (0x0 << 8) // 0x0 for sys-soft ID 387 | ((sysgeneratorID << 1) | 0x1); 388 } 389 else 390 { 391 392 p.unpack(evmRev, sensorType, sensorNum, eventType, eventData1, 393 eventData2, eventData3); 394 // Refer to IPMI Spec Table 32: SEL Event Records 395 generatorID = (ctx->channel << 12) // Channel 396 | (0x0 << 10) // Reserved 397 | ((ctx->lun & 0x3) << 8) // Lun 398 | (ctx->rqSA << 1); 399 } 400 401 if (!p.fullyUnpacked()) 402 { 403 return ipmi::responseReqDataLenInvalid(); 404 } 405 406 // Check for valid evmRev and Sensor Type(per Table 42 of spec) 407 if (evmRev != 0x04) 408 { 409 return ipmi::responseInvalidFieldRequest(); 410 } 411 if ((sensorType > 0x2C) && (sensorType < 0xC0)) 412 { 413 return ipmi::responseInvalidFieldRequest(); 414 } 415 416 // Send this request to the Redfish hooks to log it as a Redfish message 417 // instead. There is no need to add it to the SEL, so just return success. 418 intel_oem::ipmi::sel::checkRedfishHooks( 419 generatorID, evmRev, sensorType, sensorNum, eventType, eventData1, 420 eventData2.value_or(0xFF), eventData3.value_or(0xFF)); 421 422 if (((generatorID & 0xFF) >> 1) == meId && sensorNum == meSensorNum && 423 eventData2 && eventData3) 424 { 425 setMeStatus(*eventData2, *eventData3, (eventType & disabled)); 426 } 427 428 return ipmi::responseSuccess(); 429 } 430 431 ipmi::RspType<uint8_t, uint8_t, uint8_t, std::optional<uint8_t>> 432 ipmiSenGetSensorReading(ipmi::Context::ptr ctx, uint8_t sensnum) 433 { 434 std::string connection; 435 std::string path; 436 437 if (sensnum == reservedSensorNumber) 438 { 439 return ipmi::responseInvalidFieldRequest(); 440 } 441 442 auto status = getSensorConnection(ctx, sensnum, connection, path); 443 if (status) 444 { 445 return ipmi::response(status); 446 } 447 448 SensorMap sensorMap; 449 if (!getSensorMap(ctx->yield, connection, path, sensorMap)) 450 { 451 return ipmi::responseResponseError(); 452 } 453 auto sensorObject = sensorMap.find("xyz.openbmc_project.Sensor.Value"); 454 455 if (sensorObject == sensorMap.end() || 456 sensorObject->second.find("Value") == sensorObject->second.end()) 457 { 458 return ipmi::responseResponseError(); 459 } 460 auto& valueVariant = sensorObject->second["Value"]; 461 double reading = std::visit(VariantToDoubleVisitor(), valueVariant); 462 463 double max = 0; 464 double min = 0; 465 getSensorMaxMin(sensorMap, max, min); 466 467 int16_t mValue = 0; 468 int16_t bValue = 0; 469 int8_t rExp = 0; 470 int8_t bExp = 0; 471 bool bSigned = false; 472 473 if (!getSensorAttributes(max, min, mValue, rExp, bValue, bExp, bSigned)) 474 { 475 return ipmi::responseResponseError(); 476 } 477 478 uint8_t value = 479 scaleIPMIValueFromDouble(reading, mValue, rExp, bValue, bExp, bSigned); 480 uint8_t operation = 481 static_cast<uint8_t>(IPMISensorReadingByte2::sensorScanningEnable); 482 operation |= 483 static_cast<uint8_t>(IPMISensorReadingByte2::eventMessagesEnable); 484 bool notReading = std::isnan(reading); 485 486 if (!notReading) 487 { 488 auto availableObject = 489 sensorMap.find("xyz.openbmc_project.State.Decorator.Availability"); 490 if (availableObject != sensorMap.end()) 491 { 492 auto findAvailable = availableObject->second.find("Available"); 493 if (findAvailable != availableObject->second.end()) 494 { 495 bool* available = std::get_if<bool>(&(findAvailable->second)); 496 if (available && !(*available)) 497 { 498 notReading = true; 499 } 500 } 501 } 502 } 503 504 if (notReading) 505 { 506 operation |= static_cast<uint8_t>( 507 IPMISensorReadingByte2::readingStateUnavailable); 508 } 509 510 int byteValue; 511 if (bSigned) 512 { 513 byteValue = static_cast<int>(static_cast<int8_t>(value)); 514 } 515 else 516 { 517 byteValue = static_cast<int>(static_cast<uint8_t>(value)); 518 } 519 520 // Keep stats on the reading just obtained, even if it is "NaN" 521 if (details::sdrStatsTable.updateReading(sensnum, reading, byteValue)) 522 { 523 // This is the first reading, show the coefficients 524 double step = (max - min) / 255.0; 525 std::cerr << "IPMI sensor " << details::sdrStatsTable.getName(sensnum) 526 << ": Range min=" << min << " max=" << max 527 << ", step=" << step 528 << ", Coefficients mValue=" << static_cast<int>(mValue) 529 << " rExp=" << static_cast<int>(rExp) 530 << " bValue=" << static_cast<int>(bValue) 531 << " bExp=" << static_cast<int>(bExp) 532 << " bSigned=" << static_cast<int>(bSigned) << "\n"; 533 }; 534 535 uint8_t thresholds = 0; 536 537 auto warningObject = 538 sensorMap.find("xyz.openbmc_project.Sensor.Threshold.Warning"); 539 if (warningObject != sensorMap.end()) 540 { 541 auto alarmHigh = warningObject->second.find("WarningAlarmHigh"); 542 auto alarmLow = warningObject->second.find("WarningAlarmLow"); 543 if (alarmHigh != warningObject->second.end()) 544 { 545 if (std::get<bool>(alarmHigh->second)) 546 { 547 thresholds |= static_cast<uint8_t>( 548 IPMISensorReadingByte3::upperNonCritical); 549 } 550 } 551 if (alarmLow != warningObject->second.end()) 552 { 553 if (std::get<bool>(alarmLow->second)) 554 { 555 thresholds |= static_cast<uint8_t>( 556 IPMISensorReadingByte3::lowerNonCritical); 557 } 558 } 559 } 560 561 auto criticalObject = 562 sensorMap.find("xyz.openbmc_project.Sensor.Threshold.Critical"); 563 if (criticalObject != sensorMap.end()) 564 { 565 auto alarmHigh = criticalObject->second.find("CriticalAlarmHigh"); 566 auto alarmLow = criticalObject->second.find("CriticalAlarmLow"); 567 if (alarmHigh != criticalObject->second.end()) 568 { 569 if (std::get<bool>(alarmHigh->second)) 570 { 571 thresholds |= 572 static_cast<uint8_t>(IPMISensorReadingByte3::upperCritical); 573 } 574 } 575 if (alarmLow != criticalObject->second.end()) 576 { 577 if (std::get<bool>(alarmLow->second)) 578 { 579 thresholds |= 580 static_cast<uint8_t>(IPMISensorReadingByte3::lowerCritical); 581 } 582 } 583 } 584 585 // no discrete as of today so optional byte is never returned 586 return ipmi::responseSuccess(value, operation, thresholds, std::nullopt); 587 } 588 589 /** @brief implements the Set Sensor threshold command 590 * @param sensorNumber - sensor number 591 * @param lowerNonCriticalThreshMask 592 * @param lowerCriticalThreshMask 593 * @param lowerNonRecovThreshMask 594 * @param upperNonCriticalThreshMask 595 * @param upperCriticalThreshMask 596 * @param upperNonRecovThreshMask 597 * @param reserved 598 * @param lowerNonCritical - lower non-critical threshold 599 * @param lowerCritical - Lower critical threshold 600 * @param lowerNonRecoverable - Lower non recovarable threshold 601 * @param upperNonCritical - Upper non-critical threshold 602 * @param upperCritical - Upper critical 603 * @param upperNonRecoverable - Upper Non-recoverable 604 * 605 * @returns IPMI completion code 606 */ 607 ipmi::RspType<> ipmiSenSetSensorThresholds( 608 ipmi::Context::ptr ctx, uint8_t sensorNum, bool lowerNonCriticalThreshMask, 609 bool lowerCriticalThreshMask, bool lowerNonRecovThreshMask, 610 bool upperNonCriticalThreshMask, bool upperCriticalThreshMask, 611 bool upperNonRecovThreshMask, uint2_t reserved, uint8_t lowerNonCritical, 612 uint8_t lowerCritical, uint8_t lowerNonRecoverable, 613 uint8_t upperNonCritical, uint8_t upperCritical, 614 uint8_t upperNonRecoverable) 615 { 616 constexpr uint8_t thresholdMask = 0xFF; 617 618 if (sensorNum == reservedSensorNumber) 619 { 620 return ipmi::responseInvalidFieldRequest(); 621 } 622 623 if (reserved) 624 { 625 return ipmi::responseInvalidFieldRequest(); 626 } 627 628 // lower nc and upper nc not suppported on any sensor 629 if (lowerNonRecovThreshMask || upperNonRecovThreshMask) 630 { 631 return ipmi::responseInvalidFieldRequest(); 632 } 633 634 // if none of the threshold mask are set, nothing to do 635 if (!(lowerNonCriticalThreshMask | lowerCriticalThreshMask | 636 lowerNonRecovThreshMask | upperNonCriticalThreshMask | 637 upperCriticalThreshMask | upperNonRecovThreshMask)) 638 { 639 return ipmi::responseSuccess(); 640 } 641 642 std::string connection; 643 std::string path; 644 645 ipmi::Cc status = getSensorConnection(ctx, sensorNum, connection, path); 646 if (status) 647 { 648 return ipmi::response(status); 649 } 650 SensorMap sensorMap; 651 if (!getSensorMap(ctx->yield, connection, path, sensorMap)) 652 { 653 return ipmi::responseResponseError(); 654 } 655 656 double max = 0; 657 double min = 0; 658 getSensorMaxMin(sensorMap, max, min); 659 660 int16_t mValue = 0; 661 int16_t bValue = 0; 662 int8_t rExp = 0; 663 int8_t bExp = 0; 664 bool bSigned = false; 665 666 if (!getSensorAttributes(max, min, mValue, rExp, bValue, bExp, bSigned)) 667 { 668 return ipmi::responseResponseError(); 669 } 670 671 // store a vector of property name, value to set, and interface 672 std::vector<std::tuple<std::string, uint8_t, std::string>> thresholdsToSet; 673 674 // define the indexes of the tuple 675 constexpr uint8_t propertyName = 0; 676 constexpr uint8_t thresholdValue = 1; 677 constexpr uint8_t interface = 2; 678 // verifiy all needed fields are present 679 if (lowerCriticalThreshMask || upperCriticalThreshMask) 680 { 681 auto findThreshold = 682 sensorMap.find("xyz.openbmc_project.Sensor.Threshold.Critical"); 683 if (findThreshold == sensorMap.end()) 684 { 685 return ipmi::responseInvalidFieldRequest(); 686 } 687 if (lowerCriticalThreshMask) 688 { 689 auto findLower = findThreshold->second.find("CriticalLow"); 690 if (findLower == findThreshold->second.end()) 691 { 692 return ipmi::responseInvalidFieldRequest(); 693 } 694 thresholdsToSet.emplace_back("CriticalLow", lowerCritical, 695 findThreshold->first); 696 } 697 if (upperCriticalThreshMask) 698 { 699 auto findUpper = findThreshold->second.find("CriticalHigh"); 700 if (findUpper == findThreshold->second.end()) 701 { 702 return ipmi::responseInvalidFieldRequest(); 703 } 704 thresholdsToSet.emplace_back("CriticalHigh", upperCritical, 705 findThreshold->first); 706 } 707 } 708 if (lowerNonCriticalThreshMask || upperNonCriticalThreshMask) 709 { 710 auto findThreshold = 711 sensorMap.find("xyz.openbmc_project.Sensor.Threshold.Warning"); 712 if (findThreshold == sensorMap.end()) 713 { 714 return ipmi::responseInvalidFieldRequest(); 715 } 716 if (lowerNonCriticalThreshMask) 717 { 718 auto findLower = findThreshold->second.find("WarningLow"); 719 if (findLower == findThreshold->second.end()) 720 { 721 return ipmi::responseInvalidFieldRequest(); 722 } 723 thresholdsToSet.emplace_back("WarningLow", lowerNonCritical, 724 findThreshold->first); 725 } 726 if (upperNonCriticalThreshMask) 727 { 728 auto findUpper = findThreshold->second.find("WarningHigh"); 729 if (findUpper == findThreshold->second.end()) 730 { 731 return ipmi::responseInvalidFieldRequest(); 732 } 733 thresholdsToSet.emplace_back("WarningHigh", upperNonCritical, 734 findThreshold->first); 735 } 736 } 737 for (const auto& property : thresholdsToSet) 738 { 739 // from section 36.3 in the IPMI Spec, assume all linear 740 double valueToSet = ((mValue * std::get<thresholdValue>(property)) + 741 (bValue * std::pow(10.0, bExp))) * 742 std::pow(10.0, rExp); 743 setDbusProperty( 744 *getSdBus(), connection, path, std::get<interface>(property), 745 std::get<propertyName>(property), ipmi::Value(valueToSet)); 746 } 747 return ipmi::responseSuccess(); 748 } 749 750 IPMIThresholds getIPMIThresholds(const SensorMap& sensorMap) 751 { 752 IPMIThresholds resp; 753 auto warningInterface = 754 sensorMap.find("xyz.openbmc_project.Sensor.Threshold.Warning"); 755 auto criticalInterface = 756 sensorMap.find("xyz.openbmc_project.Sensor.Threshold.Critical"); 757 758 if ((warningInterface != sensorMap.end()) || 759 (criticalInterface != sensorMap.end())) 760 { 761 auto sensorPair = sensorMap.find("xyz.openbmc_project.Sensor.Value"); 762 763 if (sensorPair == sensorMap.end()) 764 { 765 // should not have been able to find a sensor not implementing 766 // the sensor object 767 throw std::runtime_error("Invalid sensor map"); 768 } 769 770 double max = 0; 771 double min = 0; 772 getSensorMaxMin(sensorMap, max, min); 773 774 int16_t mValue = 0; 775 int16_t bValue = 0; 776 int8_t rExp = 0; 777 int8_t bExp = 0; 778 bool bSigned = false; 779 780 if (!getSensorAttributes(max, min, mValue, rExp, bValue, bExp, bSigned)) 781 { 782 throw std::runtime_error("Invalid sensor atrributes"); 783 } 784 if (warningInterface != sensorMap.end()) 785 { 786 auto& warningMap = warningInterface->second; 787 788 auto warningHigh = warningMap.find("WarningHigh"); 789 auto warningLow = warningMap.find("WarningLow"); 790 791 if (warningHigh != warningMap.end()) 792 { 793 794 double value = 795 std::visit(VariantToDoubleVisitor(), warningHigh->second); 796 resp.warningHigh = scaleIPMIValueFromDouble( 797 value, mValue, rExp, bValue, bExp, bSigned); 798 } 799 if (warningLow != warningMap.end()) 800 { 801 double value = 802 std::visit(VariantToDoubleVisitor(), warningLow->second); 803 resp.warningLow = scaleIPMIValueFromDouble( 804 value, mValue, rExp, bValue, bExp, bSigned); 805 } 806 } 807 if (criticalInterface != sensorMap.end()) 808 { 809 auto& criticalMap = criticalInterface->second; 810 811 auto criticalHigh = criticalMap.find("CriticalHigh"); 812 auto criticalLow = criticalMap.find("CriticalLow"); 813 814 if (criticalHigh != criticalMap.end()) 815 { 816 double value = 817 std::visit(VariantToDoubleVisitor(), criticalHigh->second); 818 resp.criticalHigh = scaleIPMIValueFromDouble( 819 value, mValue, rExp, bValue, bExp, bSigned); 820 } 821 if (criticalLow != criticalMap.end()) 822 { 823 double value = 824 std::visit(VariantToDoubleVisitor(), criticalLow->second); 825 resp.criticalLow = scaleIPMIValueFromDouble( 826 value, mValue, rExp, bValue, bExp, bSigned); 827 } 828 } 829 } 830 return resp; 831 } 832 833 ipmi::RspType<uint8_t, // readable 834 uint8_t, // lowerNCrit 835 uint8_t, // lowerCrit 836 uint8_t, // lowerNrecoverable 837 uint8_t, // upperNC 838 uint8_t, // upperCrit 839 uint8_t> // upperNRecoverable 840 ipmiSenGetSensorThresholds(ipmi::Context::ptr ctx, uint8_t sensorNumber) 841 { 842 std::string connection; 843 std::string path; 844 845 if (sensorNumber == reservedSensorNumber) 846 { 847 return ipmi::responseInvalidFieldRequest(); 848 } 849 850 auto status = getSensorConnection(ctx, sensorNumber, connection, path); 851 if (status) 852 { 853 return ipmi::response(status); 854 } 855 856 SensorMap sensorMap; 857 if (!getSensorMap(ctx->yield, connection, path, sensorMap)) 858 { 859 return ipmi::responseResponseError(); 860 } 861 862 IPMIThresholds thresholdData; 863 try 864 { 865 thresholdData = getIPMIThresholds(sensorMap); 866 } 867 catch (const std::exception&) 868 { 869 return ipmi::responseResponseError(); 870 } 871 872 uint8_t readable = 0; 873 uint8_t lowerNC = 0; 874 uint8_t lowerCritical = 0; 875 uint8_t lowerNonRecoverable = 0; 876 uint8_t upperNC = 0; 877 uint8_t upperCritical = 0; 878 uint8_t upperNonRecoverable = 0; 879 880 if (thresholdData.warningHigh) 881 { 882 readable |= 883 1 << static_cast<uint8_t>(IPMIThresholdRespBits::upperNonCritical); 884 upperNC = *thresholdData.warningHigh; 885 } 886 if (thresholdData.warningLow) 887 { 888 readable |= 889 1 << static_cast<uint8_t>(IPMIThresholdRespBits::lowerNonCritical); 890 lowerNC = *thresholdData.warningLow; 891 } 892 893 if (thresholdData.criticalHigh) 894 { 895 readable |= 896 1 << static_cast<uint8_t>(IPMIThresholdRespBits::upperCritical); 897 upperCritical = *thresholdData.criticalHigh; 898 } 899 if (thresholdData.criticalLow) 900 { 901 readable |= 902 1 << static_cast<uint8_t>(IPMIThresholdRespBits::lowerCritical); 903 lowerCritical = *thresholdData.criticalLow; 904 } 905 906 return ipmi::responseSuccess(readable, lowerNC, lowerCritical, 907 lowerNonRecoverable, upperNC, upperCritical, 908 upperNonRecoverable); 909 } 910 911 /** @brief implements the get Sensor event enable command 912 * @param sensorNumber - sensor number 913 * 914 * @returns IPMI completion code plus response data 915 * - enabled - Sensor Event messages 916 * - assertionEnabledLsb - Assertion event messages 917 * - assertionEnabledMsb - Assertion event messages 918 * - deassertionEnabledLsb - Deassertion event messages 919 * - deassertionEnabledMsb - Deassertion event messages 920 */ 921 922 ipmi::RspType<uint8_t, // enabled 923 uint8_t, // assertionEnabledLsb 924 uint8_t, // assertionEnabledMsb 925 uint8_t, // deassertionEnabledLsb 926 uint8_t> // deassertionEnabledMsb 927 ipmiSenGetSensorEventEnable(ipmi::Context::ptr ctx, uint8_t sensorNum) 928 { 929 std::string connection; 930 std::string path; 931 932 uint8_t enabled = 0; 933 uint8_t assertionEnabledLsb = 0; 934 uint8_t assertionEnabledMsb = 0; 935 uint8_t deassertionEnabledLsb = 0; 936 uint8_t deassertionEnabledMsb = 0; 937 938 if (sensorNum == reservedSensorNumber) 939 { 940 return ipmi::responseInvalidFieldRequest(); 941 } 942 943 auto status = getSensorConnection(ctx, sensorNum, connection, path); 944 if (status) 945 { 946 return ipmi::response(status); 947 } 948 949 SensorMap sensorMap; 950 if (!getSensorMap(ctx->yield, connection, path, sensorMap)) 951 { 952 return ipmi::responseResponseError(); 953 } 954 955 auto warningInterface = 956 sensorMap.find("xyz.openbmc_project.Sensor.Threshold.Warning"); 957 auto criticalInterface = 958 sensorMap.find("xyz.openbmc_project.Sensor.Threshold.Critical"); 959 if ((warningInterface != sensorMap.end()) || 960 (criticalInterface != sensorMap.end())) 961 { 962 enabled = static_cast<uint8_t>( 963 IPMISensorEventEnableByte2::sensorScanningEnable); 964 if (warningInterface != sensorMap.end()) 965 { 966 auto& warningMap = warningInterface->second; 967 968 auto warningHigh = warningMap.find("WarningHigh"); 969 auto warningLow = warningMap.find("WarningLow"); 970 if (warningHigh != warningMap.end()) 971 { 972 assertionEnabledLsb |= static_cast<uint8_t>( 973 IPMISensorEventEnableThresholds::upperNonCriticalGoingHigh); 974 deassertionEnabledLsb |= static_cast<uint8_t>( 975 IPMISensorEventEnableThresholds::upperNonCriticalGoingLow); 976 } 977 if (warningLow != warningMap.end()) 978 { 979 assertionEnabledLsb |= static_cast<uint8_t>( 980 IPMISensorEventEnableThresholds::lowerNonCriticalGoingLow); 981 deassertionEnabledLsb |= static_cast<uint8_t>( 982 IPMISensorEventEnableThresholds::lowerNonCriticalGoingHigh); 983 } 984 } 985 if (criticalInterface != sensorMap.end()) 986 { 987 auto& criticalMap = criticalInterface->second; 988 989 auto criticalHigh = criticalMap.find("CriticalHigh"); 990 auto criticalLow = criticalMap.find("CriticalLow"); 991 992 if (criticalHigh != criticalMap.end()) 993 { 994 assertionEnabledMsb |= static_cast<uint8_t>( 995 IPMISensorEventEnableThresholds::upperCriticalGoingHigh); 996 deassertionEnabledMsb |= static_cast<uint8_t>( 997 IPMISensorEventEnableThresholds::upperCriticalGoingLow); 998 } 999 if (criticalLow != criticalMap.end()) 1000 { 1001 assertionEnabledLsb |= static_cast<uint8_t>( 1002 IPMISensorEventEnableThresholds::lowerCriticalGoingLow); 1003 deassertionEnabledLsb |= static_cast<uint8_t>( 1004 IPMISensorEventEnableThresholds::lowerCriticalGoingHigh); 1005 } 1006 } 1007 } 1008 1009 return ipmi::responseSuccess(enabled, assertionEnabledLsb, 1010 assertionEnabledMsb, deassertionEnabledLsb, 1011 deassertionEnabledMsb); 1012 } 1013 1014 /** @brief implements the get Sensor event status command 1015 * @param sensorNumber - sensor number, FFh = reserved 1016 * 1017 * @returns IPMI completion code plus response data 1018 * - sensorEventStatus - Sensor Event messages state 1019 * - assertions - Assertion event messages 1020 * - deassertions - Deassertion event messages 1021 */ 1022 ipmi::RspType<uint8_t, // sensorEventStatus 1023 std::bitset<16>, // assertions 1024 std::bitset<16> // deassertion 1025 > 1026 ipmiSenGetSensorEventStatus(ipmi::Context::ptr ctx, uint8_t sensorNum) 1027 { 1028 if (sensorNum == reservedSensorNumber) 1029 { 1030 return ipmi::responseInvalidFieldRequest(); 1031 } 1032 1033 std::string connection; 1034 std::string path; 1035 auto status = getSensorConnection(ctx, sensorNum, connection, path); 1036 if (status) 1037 { 1038 phosphor::logging::log<phosphor::logging::level::ERR>( 1039 "ipmiSenGetSensorEventStatus: Sensor connection Error", 1040 phosphor::logging::entry("SENSOR=%d", sensorNum)); 1041 return ipmi::response(status); 1042 } 1043 1044 SensorMap sensorMap; 1045 if (!getSensorMap(ctx->yield, connection, path, sensorMap)) 1046 { 1047 phosphor::logging::log<phosphor::logging::level::ERR>( 1048 "ipmiSenGetSensorEventStatus: Sensor Mapping Error", 1049 phosphor::logging::entry("SENSOR=%s", path.c_str())); 1050 return ipmi::responseResponseError(); 1051 } 1052 auto warningInterface = 1053 sensorMap.find("xyz.openbmc_project.Sensor.Threshold.Warning"); 1054 auto criticalInterface = 1055 sensorMap.find("xyz.openbmc_project.Sensor.Threshold.Critical"); 1056 1057 uint8_t sensorEventStatus = 1058 static_cast<uint8_t>(IPMISensorEventEnableByte2::sensorScanningEnable); 1059 1060 std::optional<bool> criticalDeassertHigh = 1061 thresholdDeassertMap[path]["CriticalAlarmHigh"]; 1062 std::optional<bool> criticalDeassertLow = 1063 thresholdDeassertMap[path]["CriticalAlarmLow"]; 1064 std::optional<bool> warningDeassertHigh = 1065 thresholdDeassertMap[path]["WarningAlarmHigh"]; 1066 std::optional<bool> warningDeassertLow = 1067 thresholdDeassertMap[path]["WarningAlarmLow"]; 1068 1069 std::bitset<16> assertions = 0; 1070 std::bitset<16> deassertions = 0; 1071 1072 if (criticalDeassertHigh && !*criticalDeassertHigh) 1073 { 1074 deassertions.set(static_cast<size_t>( 1075 IPMIGetSensorEventEnableThresholds::upperCriticalGoingHigh)); 1076 } 1077 if (criticalDeassertLow && !*criticalDeassertLow) 1078 { 1079 deassertions.set(static_cast<size_t>( 1080 IPMIGetSensorEventEnableThresholds::upperCriticalGoingLow)); 1081 } 1082 if (warningDeassertHigh && !*warningDeassertHigh) 1083 { 1084 deassertions.set(static_cast<size_t>( 1085 IPMIGetSensorEventEnableThresholds::upperNonCriticalGoingHigh)); 1086 } 1087 if (warningDeassertLow && !*warningDeassertLow) 1088 { 1089 deassertions.set(static_cast<size_t>( 1090 IPMIGetSensorEventEnableThresholds::lowerNonCriticalGoingHigh)); 1091 } 1092 if ((warningInterface != sensorMap.end()) || 1093 (criticalInterface != sensorMap.end())) 1094 { 1095 sensorEventStatus = static_cast<size_t>( 1096 IPMISensorEventEnableByte2::eventMessagesEnable); 1097 if (warningInterface != sensorMap.end()) 1098 { 1099 auto& warningMap = warningInterface->second; 1100 1101 auto warningHigh = warningMap.find("WarningAlarmHigh"); 1102 auto warningLow = warningMap.find("WarningAlarmLow"); 1103 auto warningHighAlarm = false; 1104 auto warningLowAlarm = false; 1105 1106 if (warningHigh != warningMap.end()) 1107 { 1108 warningHighAlarm = std::get<bool>(warningHigh->second); 1109 } 1110 if (warningLow != warningMap.end()) 1111 { 1112 warningLowAlarm = std::get<bool>(warningLow->second); 1113 } 1114 if (warningHighAlarm) 1115 { 1116 assertions.set( 1117 static_cast<size_t>(IPMIGetSensorEventEnableThresholds:: 1118 upperNonCriticalGoingHigh)); 1119 } 1120 if (warningLowAlarm) 1121 { 1122 assertions.set( 1123 static_cast<size_t>(IPMIGetSensorEventEnableThresholds:: 1124 lowerNonCriticalGoingLow)); 1125 } 1126 } 1127 if (criticalInterface != sensorMap.end()) 1128 { 1129 auto& criticalMap = criticalInterface->second; 1130 1131 auto criticalHigh = criticalMap.find("CriticalAlarmHigh"); 1132 auto criticalLow = criticalMap.find("CriticalAlarmLow"); 1133 auto criticalHighAlarm = false; 1134 auto criticalLowAlarm = false; 1135 1136 if (criticalHigh != criticalMap.end()) 1137 { 1138 criticalHighAlarm = std::get<bool>(criticalHigh->second); 1139 } 1140 if (criticalLow != criticalMap.end()) 1141 { 1142 criticalLowAlarm = std::get<bool>(criticalLow->second); 1143 } 1144 if (criticalHighAlarm) 1145 { 1146 assertions.set( 1147 static_cast<size_t>(IPMIGetSensorEventEnableThresholds:: 1148 upperCriticalGoingHigh)); 1149 } 1150 if (criticalLowAlarm) 1151 { 1152 assertions.set(static_cast<size_t>( 1153 IPMIGetSensorEventEnableThresholds::lowerCriticalGoingLow)); 1154 } 1155 } 1156 } 1157 1158 return ipmi::responseSuccess(sensorEventStatus, assertions, deassertions); 1159 } 1160 1161 static inline uint16_t getNumberOfSensors(void) 1162 { 1163 return sensorTree.size() > maxIPMISensors ? maxIPMISensors 1164 : sensorTree.size(); 1165 } 1166 1167 static int getSensorDataRecord(ipmi::Context::ptr ctx, 1168 std::vector<uint8_t>& recordData, 1169 uint16_t recordID) 1170 { 1171 size_t fruCount = 0; 1172 ipmi::Cc ret = ipmi::storage::getFruSdrCount(ctx, fruCount); 1173 if (ret != ipmi::ccSuccess) 1174 { 1175 phosphor::logging::log<phosphor::logging::level::ERR>( 1176 "getSensorDataRecord: getFruSdrCount error"); 1177 return GENERAL_ERROR; 1178 } 1179 1180 size_t lastRecord = getNumberOfSensors() + fruCount + 1181 ipmi::storage::type12Count + 1182 ipmi::storage::nmDiscoverySDRCount - 1; 1183 if (recordID == lastRecordIndex) 1184 { 1185 recordID = lastRecord; 1186 } 1187 if (recordID > lastRecord) 1188 { 1189 phosphor::logging::log<phosphor::logging::level::ERR>( 1190 "getSensorDataRecord: recordID > lastRecord error"); 1191 return GENERAL_ERROR; 1192 } 1193 1194 if (recordID >= getNumberOfSensors()) 1195 { 1196 size_t fruIndex = recordID - getNumberOfSensors(); 1197 size_t type12End = fruCount + ipmi::storage::type12Count; 1198 1199 if (fruIndex >= type12End) 1200 { 1201 // NM discovery SDR 1202 size_t nmDiscoveryIndex = fruIndex - type12End; 1203 if (nmDiscoveryIndex >= ipmi::storage::nmDiscoverySDRCount) 1204 { 1205 phosphor::logging::log<phosphor::logging::level::ERR>( 1206 "getSensorDataRecord: NM DiscoveryIndex error"); 1207 return GENERAL_ERROR; 1208 } 1209 recordData = 1210 ipmi::storage::getNMDiscoverySDR(nmDiscoveryIndex, recordID); 1211 } 1212 else if (fruIndex >= fruCount) 1213 { 1214 // handle type 12 hardcoded records 1215 size_t type12Index = fruIndex - fruCount; 1216 if (type12Index >= ipmi::storage::type12Count) 1217 { 1218 phosphor::logging::log<phosphor::logging::level::ERR>( 1219 "getSensorDataRecord: type12Index error"); 1220 return GENERAL_ERROR; 1221 } 1222 recordData = ipmi::storage::getType12SDRs(type12Index, recordID); 1223 } 1224 else 1225 { 1226 // handle fru records 1227 get_sdr::SensorDataFruRecord data; 1228 ret = ipmi::storage::getFruSdrs(ctx, fruIndex, data); 1229 if (ret != IPMI_CC_OK) 1230 { 1231 return GENERAL_ERROR; 1232 } 1233 data.header.record_id_msb = recordID >> 8; 1234 data.header.record_id_lsb = recordID & 0xFF; 1235 recordData.insert(recordData.end(), (uint8_t*)&data, 1236 ((uint8_t*)&data) + sizeof(data)); 1237 } 1238 1239 return 0; 1240 } 1241 1242 // Perform a incremental scan of the SDR Record ID's and translate the 1243 // first 765 SDR records (i.e. maxIPMISensors) into IPMI Sensor 1244 // Numbers. The IPMI sensor numbers are not linear, and have a reserved 1245 // gap at 0xff. This code creates 254 sensors per LUN, excepting LUN 2 1246 // which has special meaning. 1247 std::string connection; 1248 std::string path; 1249 uint16_t sensNumFromRecID{recordID}; 1250 if ((recordID > lun0MaxSensorNum) && (recordID < lun1MaxSensorNum)) 1251 { 1252 // LUN 0 has one reserved sensor number. Compensate here by adding one 1253 // to the record ID 1254 sensNumFromRecID = recordID + 1; 1255 ctx->lun = 1; 1256 } 1257 else if ((recordID >= lun1MaxSensorNum) && (recordID < maxIPMISensors)) 1258 { 1259 // LUN 0, 1 have a reserved sensor number. Compensate here by adding 2 1260 // to the record ID. Skip all 256 sensors in LUN 2, as it has special 1261 // rules governing its use. 1262 sensNumFromRecID = recordID + (maxSensorsPerLUN + 1) + 2; 1263 ctx->lun = 3; 1264 } 1265 1266 auto status = getSensorConnection( 1267 ctx, static_cast<uint8_t>(sensNumFromRecID), connection, path); 1268 if (status) 1269 { 1270 phosphor::logging::log<phosphor::logging::level::ERR>( 1271 "getSensorDataRecord: getSensorConnection error"); 1272 return GENERAL_ERROR; 1273 } 1274 SensorMap sensorMap; 1275 if (!getSensorMap(ctx->yield, connection, path, sensorMap, 1276 sensorMapUpdatePeriod)) 1277 { 1278 phosphor::logging::log<phosphor::logging::level::ERR>( 1279 "getSensorDataRecord: getSensorMap error"); 1280 return GENERAL_ERROR; 1281 } 1282 uint16_t sensorNum = getSensorNumberFromPath(path); 1283 // Return an error on LUN 2 assingments, and any sensor number beyond the 1284 // range of LUN 3 1285 if (((sensorNum > lun1MaxSensorNum) && (sensorNum <= maxIPMISensors)) || 1286 (sensorNum > lun3MaxSensorNum)) 1287 { 1288 phosphor::logging::log<phosphor::logging::level::ERR>( 1289 "getSensorDataRecord: invalidSensorNumber"); 1290 return GENERAL_ERROR; 1291 } 1292 uint8_t sensornumber = static_cast<uint8_t>(sensorNum); 1293 uint8_t lun = static_cast<uint8_t>(sensorNum >> 8); 1294 1295 if ((sensornumber != static_cast<uint8_t>(sensNumFromRecID)) && 1296 (lun != ctx->lun)) 1297 { 1298 phosphor::logging::log<phosphor::logging::level::ERR>( 1299 "getSensorDataRecord: sensor record mismatch"); 1300 return GENERAL_ERROR; 1301 } 1302 get_sdr::SensorDataFullRecord record = {0}; 1303 1304 get_sdr::header::set_record_id( 1305 recordID, reinterpret_cast<get_sdr::SensorDataRecordHeader*>(&record)); 1306 1307 record.header.sdr_version = ipmiSdrVersion; 1308 record.header.record_type = get_sdr::SENSOR_DATA_FULL_RECORD; 1309 record.header.record_length = sizeof(get_sdr::SensorDataFullRecord) - 1310 sizeof(get_sdr::SensorDataRecordHeader); 1311 record.key.owner_id = 0x20; 1312 record.key.owner_lun = lun; 1313 record.key.sensor_number = sensornumber; 1314 1315 record.body.sensor_capabilities = 0x68; // auto rearm - todo hysteresis 1316 record.body.sensor_type = getSensorTypeFromPath(path); 1317 std::string type = getSensorTypeStringFromPath(path); 1318 auto typeCstr = type.c_str(); 1319 auto findUnits = sensorUnits.find(typeCstr); 1320 if (findUnits != sensorUnits.end()) 1321 { 1322 record.body.sensor_units_2_base = 1323 static_cast<uint8_t>(findUnits->second); 1324 } // else default 0x0 unspecified 1325 1326 record.body.event_reading_type = getSensorEventTypeFromPath(path); 1327 1328 auto sensorObject = sensorMap.find("xyz.openbmc_project.Sensor.Value"); 1329 if (sensorObject == sensorMap.end()) 1330 { 1331 phosphor::logging::log<phosphor::logging::level::ERR>( 1332 "getSensorDataRecord: sensorObject error"); 1333 return GENERAL_ERROR; 1334 } 1335 1336 uint8_t entityId = 0; 1337 uint8_t entityInstance = 0x01; 1338 1339 // follow the association chain to get the parent board's entityid and 1340 // entityInstance 1341 updateIpmiFromAssociation(path, sensorMap, entityId, entityInstance); 1342 1343 record.body.entity_id = entityId; 1344 record.body.entity_instance = entityInstance; 1345 1346 auto maxObject = sensorObject->second.find("MaxValue"); 1347 auto minObject = sensorObject->second.find("MinValue"); 1348 1349 // If min and/or max are left unpopulated, 1350 // then default to what a signed byte would be, namely (-128,127) range. 1351 auto max = static_cast<double>(std::numeric_limits<int8_t>::max()); 1352 auto min = static_cast<double>(std::numeric_limits<int8_t>::lowest()); 1353 if (maxObject != sensorObject->second.end()) 1354 { 1355 max = std::visit(VariantToDoubleVisitor(), maxObject->second); 1356 } 1357 1358 if (minObject != sensorObject->second.end()) 1359 { 1360 min = std::visit(VariantToDoubleVisitor(), minObject->second); 1361 } 1362 1363 int16_t mValue = 0; 1364 int8_t rExp = 0; 1365 int16_t bValue = 0; 1366 int8_t bExp = 0; 1367 bool bSigned = false; 1368 1369 if (!getSensorAttributes(max, min, mValue, rExp, bValue, bExp, bSigned)) 1370 { 1371 phosphor::logging::log<phosphor::logging::level::ERR>( 1372 "getSensorDataRecord: getSensorAttributes error"); 1373 return GENERAL_ERROR; 1374 } 1375 1376 // The record.body is a struct SensorDataFullRecordBody 1377 // from sensorhandler.hpp in phosphor-ipmi-host. 1378 // The meaning of these bits appears to come from 1379 // table 43.1 of the IPMI spec. 1380 // The above 5 sensor attributes are stuffed in as follows: 1381 // Byte 21 = AA000000 = analog interpretation, 10 signed, 00 unsigned 1382 // Byte 22-24 are for other purposes 1383 // Byte 25 = MMMMMMMM = LSB of M 1384 // Byte 26 = MMTTTTTT = MSB of M (signed), and Tolerance 1385 // Byte 27 = BBBBBBBB = LSB of B 1386 // Byte 28 = BBAAAAAA = MSB of B (signed), and LSB of Accuracy 1387 // Byte 29 = AAAAEE00 = MSB of Accuracy, exponent of Accuracy 1388 // Byte 30 = RRRRBBBB = rExp (signed), bExp (signed) 1389 1390 // apply M, B, and exponents, M and B are 10 bit values, exponents are 4 1391 record.body.m_lsb = mValue & 0xFF; 1392 1393 uint8_t mBitSign = (mValue < 0) ? 1 : 0; 1394 uint8_t mBitNine = (mValue & 0x0100) >> 8; 1395 1396 // move the smallest bit of the MSB into place (bit 9) 1397 // the MSbs are bits 7:8 in m_msb_and_tolerance 1398 record.body.m_msb_and_tolerance = (mBitSign << 7) | (mBitNine << 6); 1399 1400 record.body.b_lsb = bValue & 0xFF; 1401 1402 uint8_t bBitSign = (bValue < 0) ? 1 : 0; 1403 uint8_t bBitNine = (bValue & 0x0100) >> 8; 1404 1405 // move the smallest bit of the MSB into place (bit 9) 1406 // the MSbs are bits 7:8 in b_msb_and_accuracy_lsb 1407 record.body.b_msb_and_accuracy_lsb = (bBitSign << 7) | (bBitNine << 6); 1408 1409 uint8_t rExpSign = (rExp < 0) ? 1 : 0; 1410 uint8_t rExpBits = rExp & 0x07; 1411 1412 uint8_t bExpSign = (bExp < 0) ? 1 : 0; 1413 uint8_t bExpBits = bExp & 0x07; 1414 1415 // move rExp and bExp into place 1416 record.body.r_b_exponents = 1417 (rExpSign << 7) | (rExpBits << 4) | (bExpSign << 3) | bExpBits; 1418 1419 // Set the analog reading byte interpretation accordingly 1420 record.body.sensor_units_1 = (bSigned ? 1 : 0) << 7; 1421 1422 // TODO(): Perhaps care about Tolerance, Accuracy, and so on 1423 // These seem redundant, but derivable from the above 5 attributes 1424 // Original comment said "todo fill out rest of units" 1425 1426 // populate sensor name from path 1427 std::string name; 1428 size_t nameStart = path.rfind("/"); 1429 if (nameStart != std::string::npos) 1430 { 1431 name = path.substr(nameStart + 1, std::string::npos - nameStart); 1432 } 1433 1434 std::replace(name.begin(), name.end(), '_', ' '); 1435 if (name.size() > FULL_RECORD_ID_STR_MAX_LENGTH) 1436 { 1437 // try to not truncate by replacing common words 1438 constexpr std::array<std::pair<const char*, const char*>, 2> 1439 replaceWords = {std::make_pair("Output", "Out"), 1440 std::make_pair("Input", "In")}; 1441 for (const auto& [find, replace] : replaceWords) 1442 { 1443 boost::replace_all(name, find, replace); 1444 } 1445 1446 name.resize(FULL_RECORD_ID_STR_MAX_LENGTH); 1447 } 1448 record.body.id_string_info = name.size(); 1449 std::strncpy(record.body.id_string, name.c_str(), 1450 sizeof(record.body.id_string)); 1451 1452 // Remember the sensor name, as determined for this sensor number 1453 details::sdrStatsTable.updateName(sensornumber, name); 1454 1455 IPMIThresholds thresholdData; 1456 try 1457 { 1458 thresholdData = getIPMIThresholds(sensorMap); 1459 } 1460 catch (const std::exception&) 1461 { 1462 phosphor::logging::log<phosphor::logging::level::ERR>( 1463 "getSensorDataRecord: getIPMIThresholds error"); 1464 return GENERAL_ERROR; 1465 } 1466 1467 if (thresholdData.criticalHigh) 1468 { 1469 record.body.upper_critical_threshold = *thresholdData.criticalHigh; 1470 record.body.supported_deassertions[1] |= static_cast<uint8_t>( 1471 IPMISensorEventEnableThresholds::criticalThreshold); 1472 record.body.supported_deassertions[1] |= static_cast<uint8_t>( 1473 IPMISensorEventEnableThresholds::upperCriticalGoingHigh); 1474 record.body.supported_assertions[1] |= static_cast<uint8_t>( 1475 IPMISensorEventEnableThresholds::upperCriticalGoingHigh); 1476 record.body.discrete_reading_setting_mask[0] |= 1477 static_cast<uint8_t>(IPMISensorReadingByte3::upperCritical); 1478 } 1479 if (thresholdData.warningHigh) 1480 { 1481 record.body.upper_noncritical_threshold = *thresholdData.warningHigh; 1482 record.body.supported_deassertions[1] |= static_cast<uint8_t>( 1483 IPMISensorEventEnableThresholds::nonCriticalThreshold); 1484 record.body.supported_deassertions[0] |= static_cast<uint8_t>( 1485 IPMISensorEventEnableThresholds::upperNonCriticalGoingHigh); 1486 record.body.supported_assertions[0] |= static_cast<uint8_t>( 1487 IPMISensorEventEnableThresholds::upperNonCriticalGoingHigh); 1488 record.body.discrete_reading_setting_mask[0] |= 1489 static_cast<uint8_t>(IPMISensorReadingByte3::upperNonCritical); 1490 } 1491 if (thresholdData.criticalLow) 1492 { 1493 record.body.lower_critical_threshold = *thresholdData.criticalLow; 1494 record.body.supported_assertions[1] |= static_cast<uint8_t>( 1495 IPMISensorEventEnableThresholds::criticalThreshold); 1496 record.body.supported_deassertions[0] |= static_cast<uint8_t>( 1497 IPMISensorEventEnableThresholds::lowerCriticalGoingLow); 1498 record.body.supported_assertions[0] |= static_cast<uint8_t>( 1499 IPMISensorEventEnableThresholds::lowerCriticalGoingLow); 1500 record.body.discrete_reading_setting_mask[0] |= 1501 static_cast<uint8_t>(IPMISensorReadingByte3::lowerCritical); 1502 } 1503 if (thresholdData.warningLow) 1504 { 1505 record.body.lower_noncritical_threshold = *thresholdData.warningLow; 1506 record.body.supported_assertions[1] |= static_cast<uint8_t>( 1507 IPMISensorEventEnableThresholds::nonCriticalThreshold); 1508 record.body.supported_deassertions[0] |= static_cast<uint8_t>( 1509 IPMISensorEventEnableThresholds::lowerNonCriticalGoingLow); 1510 record.body.supported_assertions[0] |= static_cast<uint8_t>( 1511 IPMISensorEventEnableThresholds::lowerNonCriticalGoingLow); 1512 record.body.discrete_reading_setting_mask[0] |= 1513 static_cast<uint8_t>(IPMISensorReadingByte3::lowerNonCritical); 1514 } 1515 1516 // everything that is readable is setable 1517 record.body.discrete_reading_setting_mask[1] = 1518 record.body.discrete_reading_setting_mask[0]; 1519 recordData.insert(recordData.end(), (uint8_t*)&record, 1520 ((uint8_t*)&record) + sizeof(record)); 1521 return 0; 1522 } 1523 1524 /** @brief implements the get SDR Info command 1525 * @param count - Operation 1526 * 1527 * @returns IPMI completion code plus response data 1528 * - sdrCount - sensor/SDR count 1529 * - lunsAndDynamicPopulation - static/Dynamic sensor population flag 1530 */ 1531 static ipmi::RspType<uint8_t, // respcount 1532 uint8_t, // dynamic population flags 1533 uint32_t // last time a sensor was added 1534 > 1535 ipmiSensorGetDeviceSdrInfo(ipmi::Context::ptr ctx, 1536 std::optional<uint8_t> count) 1537 { 1538 uint8_t sdrCount = 0; 1539 uint16_t recordID = 0; 1540 std::vector<uint8_t> record; 1541 // Sensors are dynamically allocated, and there is at least one LUN 1542 uint8_t lunsAndDynamicPopulation = 0x80; 1543 constexpr uint8_t getSdrCount = 0x01; 1544 constexpr uint8_t getSensorCount = 0x00; 1545 1546 if (!getSensorSubtree(sensorTree) || sensorTree.empty()) 1547 { 1548 return ipmi::responseResponseError(); 1549 } 1550 uint16_t numSensors = getNumberOfSensors(); 1551 if (count.value_or(0) == getSdrCount) 1552 { 1553 // Count the number of Type 1 SDR entries assigned to the LUN 1554 while (!getSensorDataRecord(ctx, record, recordID++)) 1555 { 1556 get_sdr::SensorDataRecordHeader* hdr = 1557 reinterpret_cast<get_sdr::SensorDataRecordHeader*>( 1558 record.data()); 1559 if (hdr->record_type == get_sdr::SENSOR_DATA_FULL_RECORD) 1560 { 1561 get_sdr::SensorDataFullRecord* recordData = 1562 reinterpret_cast<get_sdr::SensorDataFullRecord*>( 1563 record.data()); 1564 if (ctx->lun == recordData->key.owner_lun) 1565 { 1566 sdrCount++; 1567 } 1568 } 1569 } 1570 } 1571 else if (count.value_or(0) == getSensorCount) 1572 { 1573 // Return the number of sensors attached to the LUN 1574 if ((ctx->lun == 0) && (numSensors > 0)) 1575 { 1576 sdrCount = 1577 (numSensors > maxSensorsPerLUN) ? maxSensorsPerLUN : numSensors; 1578 } 1579 else if ((ctx->lun == 1) && (numSensors > maxSensorsPerLUN)) 1580 { 1581 sdrCount = (numSensors > (2 * maxSensorsPerLUN)) 1582 ? maxSensorsPerLUN 1583 : (numSensors - maxSensorsPerLUN) & maxSensorsPerLUN; 1584 } 1585 else if (ctx->lun == 3) 1586 { 1587 if (numSensors <= maxIPMISensors) 1588 { 1589 sdrCount = 1590 (numSensors - (2 * maxSensorsPerLUN)) & maxSensorsPerLUN; 1591 } 1592 else 1593 { 1594 // error 1595 throw std::out_of_range( 1596 "Maximum number of IPMI sensors exceeded."); 1597 } 1598 } 1599 } 1600 else 1601 { 1602 return ipmi::responseInvalidFieldRequest(); 1603 } 1604 1605 // Get Sensor count. This returns the number of sensors 1606 if (numSensors > 0) 1607 { 1608 lunsAndDynamicPopulation |= 1; 1609 } 1610 if (numSensors > maxSensorsPerLUN) 1611 { 1612 lunsAndDynamicPopulation |= 2; 1613 } 1614 if (numSensors >= (maxSensorsPerLUN * 2)) 1615 { 1616 lunsAndDynamicPopulation |= 8; 1617 } 1618 if (numSensors > maxIPMISensors) 1619 { 1620 // error 1621 throw std::out_of_range("Maximum number of IPMI sensors exceeded."); 1622 } 1623 1624 return ipmi::responseSuccess(sdrCount, lunsAndDynamicPopulation, 1625 sdrLastAdd); 1626 } 1627 1628 /* end sensor commands */ 1629 1630 /* storage commands */ 1631 1632 ipmi::RspType<uint8_t, // sdr version 1633 uint16_t, // record count 1634 uint16_t, // free space 1635 uint32_t, // most recent addition 1636 uint32_t, // most recent erase 1637 uint8_t // operationSupport 1638 > 1639 ipmiStorageGetSDRRepositoryInfo(ipmi::Context::ptr ctx) 1640 { 1641 constexpr const uint16_t unspecifiedFreeSpace = 0xFFFF; 1642 if (!getSensorSubtree(sensorTree) && sensorTree.empty()) 1643 { 1644 return ipmi::responseResponseError(); 1645 } 1646 1647 size_t fruCount = 0; 1648 ipmi::Cc ret = ipmi::storage::getFruSdrCount(ctx, fruCount); 1649 if (ret != ipmi::ccSuccess) 1650 { 1651 return ipmi::response(ret); 1652 } 1653 1654 uint16_t recordCount = getNumberOfSensors() + fruCount + 1655 ipmi::storage::type12Count + 1656 ipmi::storage::nmDiscoverySDRCount; 1657 1658 uint8_t operationSupport = static_cast<uint8_t>( 1659 SdrRepositoryInfoOps::overflow); // write not supported 1660 1661 operationSupport |= 1662 static_cast<uint8_t>(SdrRepositoryInfoOps::allocCommandSupported); 1663 operationSupport |= static_cast<uint8_t>( 1664 SdrRepositoryInfoOps::reserveSDRRepositoryCommandSupported); 1665 return ipmi::responseSuccess(ipmiSdrVersion, recordCount, 1666 unspecifiedFreeSpace, sdrLastAdd, 1667 sdrLastRemove, operationSupport); 1668 } 1669 1670 /** @brief implements the get SDR allocation info command 1671 * 1672 * @returns IPMI completion code plus response data 1673 * - allocUnits - Number of possible allocation units 1674 * - allocUnitSize - Allocation unit size in bytes. 1675 * - allocUnitFree - Number of free allocation units 1676 * - allocUnitLargestFree - Largest free block in allocation units 1677 * - maxRecordSize - Maximum record size in allocation units. 1678 */ 1679 ipmi::RspType<uint16_t, // allocUnits 1680 uint16_t, // allocUnitSize 1681 uint16_t, // allocUnitFree 1682 uint16_t, // allocUnitLargestFree 1683 uint8_t // maxRecordSize 1684 > 1685 ipmiStorageGetSDRAllocationInfo() 1686 { 1687 // 0000h unspecified number of alloc units 1688 constexpr uint16_t allocUnits = 0; 1689 1690 constexpr uint16_t allocUnitFree = 0; 1691 constexpr uint16_t allocUnitLargestFree = 0; 1692 // only allow one block at a time 1693 constexpr uint8_t maxRecordSize = 1; 1694 1695 return ipmi::responseSuccess(allocUnits, maxSDRTotalSize, allocUnitFree, 1696 allocUnitLargestFree, maxRecordSize); 1697 } 1698 1699 /** @brief implements the reserve SDR command 1700 * @returns IPMI completion code plus response data 1701 * - sdrReservationID 1702 */ 1703 ipmi::RspType<uint16_t> ipmiStorageReserveSDR() 1704 { 1705 sdrReservationID++; 1706 if (sdrReservationID == 0) 1707 { 1708 sdrReservationID++; 1709 } 1710 1711 return ipmi::responseSuccess(sdrReservationID); 1712 } 1713 1714 ipmi::RspType<uint16_t, // next record ID 1715 std::vector<uint8_t> // payload 1716 > 1717 ipmiStorageGetSDR(ipmi::Context::ptr ctx, uint16_t reservationID, 1718 uint16_t recordID, uint8_t offset, uint8_t bytesToRead) 1719 { 1720 size_t fruCount = 0; 1721 // reservation required for partial reads with non zero offset into 1722 // record 1723 if ((sdrReservationID == 0 || reservationID != sdrReservationID) && offset) 1724 { 1725 phosphor::logging::log<phosphor::logging::level::ERR>( 1726 "ipmiStorageGetSDR: responseInvalidReservationId"); 1727 return ipmi::responseInvalidReservationId(); 1728 } 1729 ipmi::Cc ret = ipmi::storage::getFruSdrCount(ctx, fruCount); 1730 if (ret != ipmi::ccSuccess) 1731 { 1732 phosphor::logging::log<phosphor::logging::level::ERR>( 1733 "ipmiStorageGetSDR: getFruSdrCount error"); 1734 return ipmi::response(ret); 1735 } 1736 1737 size_t lastRecord = getNumberOfSensors() + fruCount + 1738 ipmi::storage::type12Count + 1739 ipmi::storage::nmDiscoverySDRCount - 1; 1740 uint16_t nextRecordId = lastRecord > recordID ? recordID + 1 : 0XFFFF; 1741 1742 if (!getSensorSubtree(sensorTree) && sensorTree.empty()) 1743 { 1744 phosphor::logging::log<phosphor::logging::level::ERR>( 1745 "ipmiStorageGetSDR: getSensorSubtree error"); 1746 return ipmi::responseResponseError(); 1747 } 1748 1749 std::vector<uint8_t> record; 1750 if (getSensorDataRecord(ctx, record, recordID)) 1751 { 1752 phosphor::logging::log<phosphor::logging::level::ERR>( 1753 "ipmiStorageGetSDR: fail to get SDR"); 1754 return ipmi::responseInvalidFieldRequest(); 1755 } 1756 get_sdr::SensorDataRecordHeader* hdr = 1757 reinterpret_cast<get_sdr::SensorDataRecordHeader*>(record.data()); 1758 if (!hdr) 1759 { 1760 phosphor::logging::log<phosphor::logging::level::ERR>( 1761 "ipmiStorageGetSDR: record header is null"); 1762 return ipmi::responseSuccess(nextRecordId, record); 1763 } 1764 1765 size_t sdrLength = 1766 sizeof(get_sdr::SensorDataRecordHeader) + hdr->record_length; 1767 if (sdrLength < (offset + bytesToRead)) 1768 { 1769 bytesToRead = sdrLength - offset; 1770 } 1771 1772 uint8_t* respStart = reinterpret_cast<uint8_t*>(hdr) + offset; 1773 if (!respStart) 1774 { 1775 phosphor::logging::log<phosphor::logging::level::ERR>( 1776 "ipmiStorageGetSDR: record is null"); 1777 return ipmi::responseSuccess(nextRecordId, record); 1778 } 1779 std::vector<uint8_t> recordData(respStart, respStart + bytesToRead); 1780 1781 return ipmi::responseSuccess(nextRecordId, recordData); 1782 } 1783 /* end storage commands */ 1784 1785 void registerSensorFunctions() 1786 { 1787 // <Platform Event> 1788 ipmi::registerHandler(ipmi::prioOemBase, ipmi::netFnSensor, 1789 ipmi::sensor_event::cmdPlatformEvent, 1790 ipmi::Privilege::Operator, ipmiSenPlatformEvent); 1791 1792 // <Get Sensor Reading> 1793 ipmi::registerHandler(ipmi::prioOemBase, ipmi::netFnSensor, 1794 ipmi::sensor_event::cmdGetSensorReading, 1795 ipmi::Privilege::User, ipmiSenGetSensorReading); 1796 1797 // <Get Sensor Threshold> 1798 ipmi::registerHandler(ipmi::prioOemBase, ipmi::netFnSensor, 1799 ipmi::sensor_event::cmdGetSensorThreshold, 1800 ipmi::Privilege::User, ipmiSenGetSensorThresholds); 1801 1802 // <Set Sensor Threshold> 1803 ipmi::registerHandler(ipmi::prioOemBase, ipmi::netFnSensor, 1804 ipmi::sensor_event::cmdSetSensorThreshold, 1805 ipmi::Privilege::Operator, 1806 ipmiSenSetSensorThresholds); 1807 1808 // <Get Sensor Event Enable> 1809 ipmi::registerHandler(ipmi::prioOemBase, ipmi::netFnSensor, 1810 ipmi::sensor_event::cmdGetSensorEventEnable, 1811 ipmi::Privilege::User, ipmiSenGetSensorEventEnable); 1812 1813 // <Get Sensor Event Status> 1814 ipmi::registerHandler(ipmi::prioOemBase, ipmi::netFnSensor, 1815 ipmi::sensor_event::cmdGetSensorEventStatus, 1816 ipmi::Privilege::User, ipmiSenGetSensorEventStatus); 1817 1818 // register all storage commands for both Sensor and Storage command 1819 // versions 1820 1821 // <Get SDR Repository Info> 1822 ipmi::registerHandler(ipmi::prioOemBase, ipmi::netFnStorage, 1823 ipmi::storage::cmdGetSdrRepositoryInfo, 1824 ipmi::Privilege::User, 1825 ipmiStorageGetSDRRepositoryInfo); 1826 1827 // <Get Device SDR Info> 1828 ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnSensor, 1829 ipmi::sensor_event::cmdGetDeviceSdrInfo, 1830 ipmi::Privilege::User, ipmiSensorGetDeviceSdrInfo); 1831 1832 // <Get SDR Allocation Info> 1833 ipmi::registerHandler(ipmi::prioOemBase, ipmi::netFnStorage, 1834 ipmi::storage::cmdGetSdrRepositoryAllocInfo, 1835 ipmi::Privilege::User, 1836 ipmiStorageGetSDRAllocationInfo); 1837 1838 // <Reserve SDR Repo> 1839 ipmi::registerHandler(ipmi::prioOemBase, ipmi::netFnSensor, 1840 ipmi::sensor_event::cmdReserveDeviceSdrRepository, 1841 ipmi::Privilege::User, ipmiStorageReserveSDR); 1842 1843 ipmi::registerHandler(ipmi::prioOemBase, ipmi::netFnStorage, 1844 ipmi::storage::cmdReserveSdrRepository, 1845 ipmi::Privilege::User, ipmiStorageReserveSDR); 1846 1847 // <Get Sdr> 1848 ipmi::registerHandler(ipmi::prioOemBase, ipmi::netFnSensor, 1849 ipmi::sensor_event::cmdGetDeviceSdr, 1850 ipmi::Privilege::User, ipmiStorageGetSDR); 1851 1852 ipmi::registerHandler(ipmi::prioOemBase, ipmi::netFnStorage, 1853 ipmi::storage::cmdGetSdr, ipmi::Privilege::User, 1854 ipmiStorageGetSDR); 1855 } 1856 } // namespace ipmi 1857