1 /* 2 // Copyright (c) 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 "commandutils.hpp" 18 #include "types.hpp" 19 20 #include <boost/algorithm/string.hpp> 21 #include <boost/bimap.hpp> 22 #include <boost/container/flat_map.hpp> 23 #include <phosphor-logging/log.hpp> 24 #include <sdbusplus/bus/match.hpp> 25 26 #include <cstdio> 27 #include <cstring> 28 #include <exception> 29 #include <filesystem> 30 #include <map> 31 #include <string> 32 #include <string_view> 33 #include <vector> 34 35 #pragma once 36 37 struct CmpStrVersion 38 { 39 bool operator()(std::string a, std::string b) const 40 { 41 return strverscmp(a.c_str(), b.c_str()) < 0; 42 } 43 }; 44 45 using SensorSubTree = boost::container::flat_map< 46 std::string, 47 boost::container::flat_map<std::string, std::vector<std::string>>, 48 CmpStrVersion>; 49 50 using SensorNumMap = boost::bimap<int, std::string>; 51 52 static constexpr uint16_t maxSensorsPerLUN = 255; 53 static constexpr uint16_t maxIPMISensors = (maxSensorsPerLUN * 3); 54 static constexpr uint16_t lun1Sensor0 = 0x100; 55 static constexpr uint16_t lun3Sensor0 = 0x300; 56 static constexpr uint16_t invalidSensorNumber = 0xFFFF; 57 static constexpr uint8_t reservedSensorNumber = 0xFF; 58 59 namespace details 60 { 61 62 // Enable/disable the logging of stats instrumentation 63 static constexpr bool enableInstrumentation = false; 64 65 class IPMIStatsEntry 66 { 67 private: 68 int numReadings = 0; 69 int numMissings = 0; 70 int numStreakRead = 0; 71 int numStreakMiss = 0; 72 double minValue = 0.0; 73 double maxValue = 0.0; 74 std::string sensorName; 75 76 public: 77 const std::string& getName(void) const 78 { 79 return sensorName; 80 } 81 82 void updateName(std::string_view name) 83 { 84 sensorName = name; 85 } 86 87 // Returns true if this is the first successful reading 88 // This is so the caller can log the coefficients used 89 bool updateReading(double reading, int raw) 90 { 91 if constexpr (!enableInstrumentation) 92 { 93 return false; 94 } 95 96 bool first = ((numReadings == 0) && (numMissings == 0)); 97 98 // Sensors can use "nan" to indicate unavailable reading 99 if (!(std::isfinite(reading))) 100 { 101 // Only show this if beginning a new streak 102 if (numStreakMiss == 0) 103 { 104 std::cerr << "IPMI sensor " << sensorName 105 << ": Missing reading, byte=" << raw 106 << ", Reading counts good=" << numReadings 107 << " miss=" << numMissings 108 << ", Prior good streak=" << numStreakRead << "\n"; 109 } 110 111 numStreakRead = 0; 112 ++numMissings; 113 ++numStreakMiss; 114 115 return first; 116 } 117 118 // Only show this if beginning a new streak and not the first time 119 if ((numStreakRead == 0) && (numReadings != 0)) 120 { 121 std::cerr << "IPMI sensor " << sensorName 122 << ": Recovered reading, value=" << reading 123 << " byte=" << raw 124 << ", Reading counts good=" << numReadings 125 << " miss=" << numMissings 126 << ", Prior miss streak=" << numStreakMiss << "\n"; 127 } 128 129 // Initialize min/max if the first successful reading 130 if (numReadings == 0) 131 { 132 std::cerr << "IPMI sensor " << sensorName 133 << ": First reading, value=" << reading << " byte=" << raw 134 << "\n"; 135 136 minValue = reading; 137 maxValue = reading; 138 } 139 140 numStreakMiss = 0; 141 ++numReadings; 142 ++numStreakRead; 143 144 // Only provide subsequent output if new min/max established 145 if (reading < minValue) 146 { 147 std::cerr << "IPMI sensor " << sensorName 148 << ": Lowest reading, value=" << reading 149 << " byte=" << raw << "\n"; 150 151 minValue = reading; 152 } 153 154 if (reading > maxValue) 155 { 156 std::cerr << "IPMI sensor " << sensorName 157 << ": Highest reading, value=" << reading 158 << " byte=" << raw << "\n"; 159 160 maxValue = reading; 161 } 162 163 return first; 164 } 165 }; 166 167 class IPMIStatsTable 168 { 169 private: 170 std::vector<IPMIStatsEntry> entries; 171 172 private: 173 void padEntries(size_t index) 174 { 175 char hexbuf[16]; 176 177 // Pad vector until entries[index] becomes a valid index 178 while (entries.size() <= index) 179 { 180 // As name not known yet, use human-readable hex as name 181 IPMIStatsEntry newEntry; 182 sprintf(hexbuf, "0x%02zX", entries.size()); 183 newEntry.updateName(hexbuf); 184 185 entries.push_back(std::move(newEntry)); 186 } 187 } 188 189 public: 190 void wipeTable(void) 191 { 192 entries.clear(); 193 } 194 195 const std::string& getName(size_t index) 196 { 197 padEntries(index); 198 return entries[index].getName(); 199 } 200 201 void updateName(size_t index, std::string_view name) 202 { 203 padEntries(index); 204 entries[index].updateName(name); 205 } 206 207 bool updateReading(size_t index, double reading, int raw) 208 { 209 padEntries(index); 210 return entries[index].updateReading(reading, raw); 211 } 212 }; 213 214 // This object is global singleton, used from a variety of places 215 inline IPMIStatsTable sdrStatsTable; 216 217 inline static uint16_t getSensorSubtree(std::shared_ptr<SensorSubTree>& subtree) 218 { 219 static std::shared_ptr<SensorSubTree> sensorTreePtr; 220 static uint16_t sensorUpdatedIndex = 0; 221 sd_bus* bus = NULL; 222 int ret = sd_bus_default_system(&bus); 223 if (ret < 0) 224 { 225 phosphor::logging::log<phosphor::logging::level::ERR>( 226 "Failed to connect to system bus", 227 phosphor::logging::entry("ERRNO=0x%X", -ret)); 228 sd_bus_unref(bus); 229 return sensorUpdatedIndex; 230 } 231 sdbusplus::bus_t dbus(bus); 232 static sdbusplus::bus::match_t sensorAdded( 233 dbus, 234 "type='signal',member='InterfacesAdded',arg0path='/xyz/openbmc_project/" 235 "sensors/'", 236 [](sdbusplus::message_t& m) { sensorTreePtr.reset(); }); 237 238 static sdbusplus::bus::match_t sensorRemoved( 239 dbus, 240 "type='signal',member='InterfacesRemoved',arg0path='/xyz/" 241 "openbmc_project/sensors/'", 242 [](sdbusplus::message_t& m) { sensorTreePtr.reset(); }); 243 244 if (sensorTreePtr) 245 { 246 subtree = sensorTreePtr; 247 return sensorUpdatedIndex; 248 } 249 250 sensorTreePtr = std::make_shared<SensorSubTree>(); 251 252 auto mapperCall = dbus.new_method_call("xyz.openbmc_project.ObjectMapper", 253 "/xyz/openbmc_project/object_mapper", 254 "xyz.openbmc_project.ObjectMapper", 255 "GetSubTree"); 256 static constexpr const auto depth = 2; 257 static constexpr std::array<const char*, 3> interfaces = { 258 "xyz.openbmc_project.Sensor.Value", 259 "xyz.openbmc_project.Sensor.Threshold.Warning", 260 "xyz.openbmc_project.Sensor.Threshold.Critical"}; 261 mapperCall.append("/xyz/openbmc_project/sensors", depth, interfaces); 262 263 try 264 { 265 auto mapperReply = dbus.call(mapperCall); 266 mapperReply.read(*sensorTreePtr); 267 } 268 catch (const sdbusplus::exception_t& e) 269 { 270 phosphor::logging::log<phosphor::logging::level::ERR>(e.what()); 271 return sensorUpdatedIndex; 272 } 273 subtree = sensorTreePtr; 274 sensorUpdatedIndex++; 275 // The SDR is being regenerated, wipe the old stats 276 sdrStatsTable.wipeTable(); 277 return sensorUpdatedIndex; 278 } 279 280 inline static bool getSensorNumMap(std::shared_ptr<SensorNumMap>& sensorNumMap) 281 { 282 static std::shared_ptr<SensorNumMap> sensorNumMapPtr; 283 bool sensorNumMapUpated = false; 284 static uint16_t prevSensorUpdatedIndex = 0; 285 std::shared_ptr<SensorSubTree> sensorTree; 286 uint16_t curSensorUpdatedIndex = details::getSensorSubtree(sensorTree); 287 if (!sensorTree) 288 { 289 return sensorNumMapUpated; 290 } 291 292 if ((curSensorUpdatedIndex == prevSensorUpdatedIndex) && sensorNumMapPtr) 293 { 294 sensorNumMap = sensorNumMapPtr; 295 return sensorNumMapUpated; 296 } 297 prevSensorUpdatedIndex = curSensorUpdatedIndex; 298 299 sensorNumMapPtr = std::make_shared<SensorNumMap>(); 300 301 uint16_t sensorNum = 0; 302 uint16_t sensorIndex = 0; 303 for (const auto& sensor : *sensorTree) 304 { 305 sensorNumMapPtr->insert( 306 SensorNumMap::value_type(sensorNum, sensor.first)); 307 sensorIndex++; 308 if (sensorIndex == maxSensorsPerLUN) 309 { 310 sensorIndex = lun1Sensor0; 311 } 312 else if (sensorIndex == (lun1Sensor0 | maxSensorsPerLUN)) 313 { 314 // Skip assigning LUN 0x2 any sensors 315 sensorIndex = lun3Sensor0; 316 } 317 else if (sensorIndex == (lun3Sensor0 | maxSensorsPerLUN)) 318 { 319 // this is an error, too many IPMI sensors 320 throw std::out_of_range("Maximum number of IPMI sensors exceeded."); 321 } 322 sensorNum = sensorIndex; 323 } 324 sensorNumMap = sensorNumMapPtr; 325 sensorNumMapUpated = true; 326 return sensorNumMapUpated; 327 } 328 } // namespace details 329 330 inline static bool getSensorSubtree(SensorSubTree& subtree) 331 { 332 std::shared_ptr<SensorSubTree> sensorTree; 333 details::getSensorSubtree(sensorTree); 334 if (!sensorTree) 335 { 336 return false; 337 } 338 339 subtree = *sensorTree; 340 return true; 341 } 342 343 struct CmpStr 344 { 345 bool operator()(const char* a, const char* b) const 346 { 347 return std::strcmp(a, b) < 0; 348 } 349 }; 350 351 enum class SensorTypeCodes : uint8_t 352 { 353 reserved = 0x0, 354 temperature = 0x1, 355 voltage = 0x2, 356 current = 0x3, 357 fan = 0x4, 358 other = 0xB, 359 }; 360 361 const static boost::container::flat_map<const char*, SensorTypeCodes, CmpStr> 362 sensorTypes{{{"temperature", SensorTypeCodes::temperature}, 363 {"voltage", SensorTypeCodes::voltage}, 364 {"current", SensorTypeCodes::current}, 365 {"fan_tach", SensorTypeCodes::fan}, 366 {"fan_pwm", SensorTypeCodes::fan}, 367 {"power", SensorTypeCodes::other}}}; 368 369 inline static std::string getSensorTypeStringFromPath(const std::string& path) 370 { 371 // get sensor type string from path, path is defined as 372 // /xyz/openbmc_project/sensors/<type>/label 373 size_t typeEnd = path.rfind("/"); 374 if (typeEnd == std::string::npos) 375 { 376 return path; 377 } 378 size_t typeStart = path.rfind("/", typeEnd - 1); 379 if (typeStart == std::string::npos) 380 { 381 return path; 382 } 383 // Start at the character after the '/' 384 typeStart++; 385 return path.substr(typeStart, typeEnd - typeStart); 386 } 387 388 inline static uint8_t getSensorTypeFromPath(const std::string& path) 389 { 390 uint8_t sensorType = 0; 391 std::string type = getSensorTypeStringFromPath(path); 392 auto findSensor = sensorTypes.find(type.c_str()); 393 if (findSensor != sensorTypes.end()) 394 { 395 sensorType = static_cast<uint8_t>(findSensor->second); 396 } // else default 0x0 RESERVED 397 398 return sensorType; 399 } 400 401 inline static uint16_t getSensorNumberFromPath(const std::string& path) 402 { 403 std::shared_ptr<SensorNumMap> sensorNumMapPtr; 404 details::getSensorNumMap(sensorNumMapPtr); 405 if (!sensorNumMapPtr) 406 { 407 return invalidSensorNumber; 408 } 409 410 try 411 { 412 return sensorNumMapPtr->right.at(path); 413 } 414 catch (const std::out_of_range& e) 415 { 416 phosphor::logging::log<phosphor::logging::level::ERR>(e.what()); 417 return invalidSensorNumber; 418 } 419 } 420 421 inline static uint8_t getSensorEventTypeFromPath(const std::string& path) 422 { 423 // TODO: Add support for additional reading types as needed 424 return 0x1; // reading type = threshold 425 } 426 427 inline static std::string getPathFromSensorNumber(uint16_t sensorNum) 428 { 429 std::shared_ptr<SensorNumMap> sensorNumMapPtr; 430 details::getSensorNumMap(sensorNumMapPtr); 431 if (!sensorNumMapPtr) 432 { 433 return std::string(); 434 } 435 436 try 437 { 438 return sensorNumMapPtr->left.at(sensorNum); 439 } 440 catch (const std::out_of_range& e) 441 { 442 phosphor::logging::log<phosphor::logging::level::ERR>(e.what()); 443 return std::string(); 444 } 445 } 446 447 namespace ipmi 448 { 449 450 static inline std::map<std::string, std::vector<std::string>> 451 getObjectInterfaces(const char* path) 452 { 453 std::map<std::string, std::vector<std::string>> interfacesResponse; 454 std::vector<std::string> interfaces; 455 std::shared_ptr<sdbusplus::asio::connection> dbus = getSdBus(); 456 457 sdbusplus::message_t getObjectMessage = 458 dbus->new_method_call("xyz.openbmc_project.ObjectMapper", 459 "/xyz/openbmc_project/object_mapper", 460 "xyz.openbmc_project.ObjectMapper", "GetObject"); 461 getObjectMessage.append(path, interfaces); 462 463 try 464 { 465 sdbusplus::message_t response = dbus->call(getObjectMessage); 466 response.read(interfacesResponse); 467 } 468 catch (const std::exception& e) 469 { 470 phosphor::logging::log<phosphor::logging::level::ERR>( 471 "Failed to GetObject", phosphor::logging::entry("PATH=%s", path), 472 phosphor::logging::entry("WHAT=%s", e.what())); 473 } 474 475 return interfacesResponse; 476 } 477 478 static inline std::map<std::string, DbusVariant> 479 getEntityManagerProperties(const char* path, const char* interface) 480 { 481 std::map<std::string, DbusVariant> properties; 482 std::shared_ptr<sdbusplus::asio::connection> dbus = getSdBus(); 483 484 sdbusplus::message_t getProperties = 485 dbus->new_method_call("xyz.openbmc_project.EntityManager", path, 486 "org.freedesktop.DBus.Properties", "GetAll"); 487 getProperties.append(interface); 488 489 try 490 { 491 sdbusplus::message_t response = dbus->call(getProperties); 492 response.read(properties); 493 } 494 catch (const std::exception& e) 495 { 496 phosphor::logging::log<phosphor::logging::level::ERR>( 497 "Failed to GetAll", phosphor::logging::entry("PATH=%s", path), 498 phosphor::logging::entry("INTF=%s", interface), 499 phosphor::logging::entry("WHAT=%s", e.what())); 500 } 501 502 return properties; 503 } 504 505 static inline const std::string* getSensorConfigurationInterface( 506 const std::map<std::string, std::vector<std::string>>& 507 sensorInterfacesResponse) 508 { 509 auto entityManagerService = 510 sensorInterfacesResponse.find("xyz.openbmc_project.EntityManager"); 511 if (entityManagerService == sensorInterfacesResponse.end()) 512 { 513 return nullptr; 514 } 515 516 // Find the fan configuration first (fans can have multiple configuration 517 // interfaces). 518 for (const auto& entry : entityManagerService->second) 519 { 520 if (entry == "xyz.openbmc_project.Configuration.AspeedFan" || 521 entry == "xyz.openbmc_project.Configuration.I2CFan" || 522 entry == "xyz.openbmc_project.Configuration.NuvotonFan") 523 { 524 return &entry; 525 } 526 } 527 528 for (const auto& entry : entityManagerService->second) 529 { 530 if (boost::algorithm::starts_with(entry, 531 "xyz.openbmc_project.Configuration.")) 532 { 533 return &entry; 534 } 535 } 536 537 return nullptr; 538 } 539 540 // Follow Association properties for Sensor back to the Board dbus object to 541 // check for an EntityId and EntityInstance property. 542 static inline void updateIpmiFromAssociation(const std::string& path, 543 const SensorMap& sensorMap, 544 uint8_t& entityId, 545 uint8_t& entityInstance) 546 { 547 namespace fs = std::filesystem; 548 549 auto sensorAssociationObject = 550 sensorMap.find("xyz.openbmc_project.Association.Definitions"); 551 if (sensorAssociationObject == sensorMap.end()) 552 { 553 if constexpr (debug) 554 { 555 std::fprintf(stderr, "path=%s, no association interface found\n", 556 path.c_str()); 557 } 558 559 return; 560 } 561 562 auto associationObject = 563 sensorAssociationObject->second.find("Associations"); 564 if (associationObject == sensorAssociationObject->second.end()) 565 { 566 if constexpr (debug) 567 { 568 std::fprintf(stderr, "path=%s, no association records found\n", 569 path.c_str()); 570 } 571 572 return; 573 } 574 575 std::vector<Association> associationValues = 576 std::get<std::vector<Association>>(associationObject->second); 577 578 // loop through the Associations looking for the right one: 579 for (const auto& entry : associationValues) 580 { 581 // forward, reverse, endpoint 582 const std::string& forward = std::get<0>(entry); 583 const std::string& reverse = std::get<1>(entry); 584 const std::string& endpoint = std::get<2>(entry); 585 586 // We only currently concern ourselves with chassis+all_sensors. 587 if (!(forward == "chassis" && reverse == "all_sensors")) 588 { 589 continue; 590 } 591 592 // the endpoint is the board entry provided by 593 // Entity-Manager. so let's grab its properties if it has 594 // the right interface. 595 596 // just try grabbing the properties first. 597 std::map<std::string, DbusVariant> ipmiProperties = 598 getEntityManagerProperties( 599 endpoint.c_str(), 600 "xyz.openbmc_project.Inventory.Decorator.Ipmi"); 601 602 auto entityIdProp = ipmiProperties.find("EntityId"); 603 auto entityInstanceProp = ipmiProperties.find("EntityInstance"); 604 if (entityIdProp != ipmiProperties.end()) 605 { 606 entityId = 607 static_cast<uint8_t>(std::get<uint64_t>(entityIdProp->second)); 608 } 609 if (entityInstanceProp != ipmiProperties.end()) 610 { 611 entityInstance = static_cast<uint8_t>( 612 std::get<uint64_t>(entityInstanceProp->second)); 613 } 614 615 // Now check the entity-manager entry for this sensor to see 616 // if it has its own value and use that instead. 617 // 618 // In theory, checking this first saves us from checking 619 // both, except in most use-cases identified, there won't be 620 // a per sensor override, so we need to always check both. 621 std::string sensorNameFromPath = fs::path(path).filename(); 622 623 std::string sensorConfigPath = endpoint + "/" + sensorNameFromPath; 624 625 // Download the interfaces for the sensor from 626 // Entity-Manager to find the name of the configuration 627 // interface. 628 std::map<std::string, std::vector<std::string>> 629 sensorInterfacesResponse = 630 getObjectInterfaces(sensorConfigPath.c_str()); 631 632 const std::string* configurationInterface = 633 getSensorConfigurationInterface(sensorInterfacesResponse); 634 635 // We didnt' find a configuration interface for this sensor, but we 636 // followed the Association property to get here, so we're done 637 // searching. 638 if (!configurationInterface) 639 { 640 break; 641 } 642 643 // We found a configuration interface. 644 std::map<std::string, DbusVariant> configurationProperties = 645 getEntityManagerProperties(sensorConfigPath.c_str(), 646 configurationInterface->c_str()); 647 648 entityIdProp = configurationProperties.find("EntityId"); 649 entityInstanceProp = configurationProperties.find("EntityInstance"); 650 if (entityIdProp != configurationProperties.end()) 651 { 652 entityId = 653 static_cast<uint8_t>(std::get<uint64_t>(entityIdProp->second)); 654 } 655 if (entityInstanceProp != configurationProperties.end()) 656 { 657 entityInstance = static_cast<uint8_t>( 658 std::get<uint64_t>(entityInstanceProp->second)); 659 } 660 661 // stop searching Association records. 662 break; 663 } // end for Association vectors. 664 665 if constexpr (debug) 666 { 667 std::fprintf(stderr, "path=%s, entityId=%d, entityInstance=%d\n", 668 path.c_str(), entityId, entityInstance); 669 } 670 } 671 672 } // namespace ipmi 673