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 "dbus-sdr/sdrutils.hpp" 18 19 #include <ipmid/utils.hpp> 20 #include <nlohmann/json.hpp> 21 22 #include <fstream> 23 #include <optional> 24 #include <unordered_set> 25 26 #ifdef FEATURE_HYBRID_SENSORS 27 28 #include <ipmid/utils.hpp> 29 namespace ipmi 30 { 31 namespace sensor 32 { 33 extern const IdInfoMap sensors; 34 } // namespace sensor 35 } // namespace ipmi 36 37 #endif 38 39 boost::container::flat_map< 40 const char*, std::pair<SensorTypeCodes, SensorEventTypeCodes>, CmpStr> 41 sensorTypes{ 42 {{"temperature", std::make_pair(SensorTypeCodes::temperature, 43 SensorEventTypeCodes::threshold)}, 44 {"voltage", std::make_pair(SensorTypeCodes::voltage, 45 SensorEventTypeCodes::threshold)}, 46 {"current", std::make_pair(SensorTypeCodes::current, 47 SensorEventTypeCodes::threshold)}, 48 {"fan_tach", std::make_pair(SensorTypeCodes::fan, 49 SensorEventTypeCodes::threshold)}, 50 {"fan_pwm", std::make_pair(SensorTypeCodes::fan, 51 SensorEventTypeCodes::threshold)}, 52 {"intrusion", std::make_pair(SensorTypeCodes::physical_security, 53 SensorEventTypeCodes::sensorSpecified)}, 54 {"processor", std::make_pair(SensorTypeCodes::processor, 55 SensorEventTypeCodes::sensorSpecified)}, 56 {"power", std::make_pair(SensorTypeCodes::other, 57 SensorEventTypeCodes::threshold)}, 58 {"memory", std::make_pair(SensorTypeCodes::memory, 59 SensorEventTypeCodes::sensorSpecified)}, 60 {"state", std::make_pair(SensorTypeCodes::power_unit, 61 SensorEventTypeCodes::sensorSpecified)}, 62 {"buttons", std::make_pair(SensorTypeCodes::buttons, 63 SensorEventTypeCodes::sensorSpecified)}, 64 {"watchdog", std::make_pair(SensorTypeCodes::watchdog2, 65 SensorEventTypeCodes::sensorSpecified)}, 66 {"entity", std::make_pair(SensorTypeCodes::entity, 67 SensorEventTypeCodes::sensorSpecified)}, 68 {"energy", std::make_pair(SensorTypeCodes::other, 69 SensorEventTypeCodes::threshold)}}}; 70 71 namespace details 72 { 73 74 // IPMI supports a smaller number of sensors than are available via Redfish. 75 // Trim the list of sensors, via a configuration file. 76 // Read the IPMI Sensor Filtering section in docs/configuration.md for 77 // a more detailed description. 78 static void filterSensors(SensorSubTree& subtree) 79 { 80 constexpr const char* filterFilename = 81 "/usr/share/ipmi-providers/sensor_filter.json"; 82 std::ifstream filterFile(filterFilename); 83 if (!filterFile.good()) 84 { 85 return; 86 } 87 nlohmann::json sensorFilterJSON = nlohmann::json::parse(filterFile, nullptr, 88 false); 89 nlohmann::json::iterator svcFilterit = 90 sensorFilterJSON.find("ServiceFilter"); 91 if (svcFilterit == sensorFilterJSON.end()) 92 { 93 return; 94 } 95 96 subtree.erase(std::remove_if(subtree.begin(), subtree.end(), 97 [svcFilterit](SensorSubTree::value_type& kv) { 98 auto& [_, serviceToIfaces] = kv; 99 100 for (auto service = svcFilterit->begin(); service != svcFilterit->end(); 101 ++service) 102 { 103 serviceToIfaces.erase(*service); 104 } 105 return serviceToIfaces.empty(); 106 }), 107 subtree.end()); 108 } 109 110 uint16_t getSensorSubtree(std::shared_ptr<SensorSubTree>& subtree) 111 { 112 static std::shared_ptr<SensorSubTree> sensorTreePtr; 113 static uint16_t sensorUpdatedIndex = 0; 114 std::shared_ptr<sdbusplus::asio::connection> dbus = getSdBus(); 115 static sdbusplus::bus::match_t sensorAdded( 116 *dbus, 117 "type='signal',member='InterfacesAdded',arg0path='/xyz/openbmc_project/" 118 "sensors/'", 119 [](sdbusplus::message_t&) { sensorTreePtr.reset(); }); 120 121 static sdbusplus::bus::match_t sensorRemoved( 122 *dbus, 123 "type='signal',member='InterfacesRemoved',arg0path='/xyz/" 124 "openbmc_project/sensors/'", 125 [](sdbusplus::message_t&) { sensorTreePtr.reset(); }); 126 127 if (sensorTreePtr) 128 { 129 subtree = sensorTreePtr; 130 return sensorUpdatedIndex; 131 } 132 133 sensorTreePtr = std::make_shared<SensorSubTree>(); 134 135 static constexpr const int32_t depth = 2; 136 137 auto lbdUpdateSensorTree = [&dbus](const char* path, 138 const auto& interfaces) { 139 auto mapperCall = dbus->new_method_call( 140 "xyz.openbmc_project.ObjectMapper", 141 "/xyz/openbmc_project/object_mapper", 142 "xyz.openbmc_project.ObjectMapper", "GetSubTree"); 143 SensorSubTree sensorTreePartial; 144 145 mapperCall.append(path, depth, interfaces); 146 147 try 148 { 149 auto mapperReply = dbus->call(mapperCall); 150 mapperReply.read(sensorTreePartial); 151 } 152 catch (const sdbusplus::exception_t& e) 153 { 154 phosphor::logging::log<phosphor::logging::level::ERR>( 155 "fail to update subtree", 156 phosphor::logging::entry("PATH=%s", path), 157 phosphor::logging::entry("WHAT=%s", e.what())); 158 return false; 159 } 160 if constexpr (debug) 161 { 162 std::fprintf(stderr, "IPMI updated: %zu sensors under %s\n", 163 sensorTreePartial.size(), path); 164 } 165 sensorTreePtr->merge(std::move(sensorTreePartial)); 166 return true; 167 }; 168 169 // Add sensors to SensorTree 170 static constexpr const std::array sensorInterfaces = { 171 "xyz.openbmc_project.Sensor.Value", 172 "xyz.openbmc_project.Sensor.ValueMutability", 173 "xyz.openbmc_project.Sensor.Threshold.Warning", 174 "xyz.openbmc_project.Sensor.Threshold.Critical"}; 175 static constexpr const std::array vrInterfaces = { 176 "xyz.openbmc_project.Control.VoltageRegulatorMode"}; 177 178 bool sensorRez = lbdUpdateSensorTree("/xyz/openbmc_project/sensors", 179 sensorInterfaces); 180 181 #ifdef FEATURE_HYBRID_SENSORS 182 183 if (!ipmi::sensor::sensors.empty()) 184 { 185 for (const auto& sensor : ipmi::sensor::sensors) 186 { 187 // Threshold sensors should not be emplaced in here. 188 if (boost::starts_with(sensor.second.sensorPath, 189 "/xyz/openbmc_project/sensors/")) 190 { 191 continue; 192 } 193 194 // The bus service name is not listed in ipmi::sensor::Info. Give it 195 // an empty string. For those function using non-threshold sensors, 196 // the bus service name will be retrieved in an alternative way. 197 boost::container::flat_map<std::string, std::vector<std::string>> 198 connectionMap{ 199 {"", {sensor.second.propertyInterfaces.begin()->first}}}; 200 sensorTreePtr->emplace(sensor.second.sensorPath, connectionMap); 201 } 202 } 203 204 #endif 205 206 // Error if searching for sensors failed. 207 if (!sensorRez) 208 { 209 return sensorUpdatedIndex; 210 } 211 212 filterSensors(*sensorTreePtr); 213 // Add VR control as optional search path. 214 (void)lbdUpdateSensorTree("/xyz/openbmc_project/vr", vrInterfaces); 215 216 subtree = sensorTreePtr; 217 sensorUpdatedIndex++; 218 // The SDR is being regenerated, wipe the old stats 219 sdrStatsTable.wipeTable(); 220 sdrWriteTable.wipeTable(); 221 return sensorUpdatedIndex; 222 } 223 224 bool getSensorNumMap(std::shared_ptr<SensorNumMap>& sensorNumMap) 225 { 226 static std::shared_ptr<SensorNumMap> sensorNumMapPtr; 227 bool sensorNumMapUpated = false; 228 static uint16_t prevSensorUpdatedIndex = 0; 229 std::shared_ptr<SensorSubTree> sensorTree; 230 uint16_t curSensorUpdatedIndex = details::getSensorSubtree(sensorTree); 231 if (!sensorTree) 232 { 233 return sensorNumMapUpated; 234 } 235 236 if ((curSensorUpdatedIndex == prevSensorUpdatedIndex) && sensorNumMapPtr) 237 { 238 sensorNumMap = sensorNumMapPtr; 239 return sensorNumMapUpated; 240 } 241 prevSensorUpdatedIndex = curSensorUpdatedIndex; 242 243 sensorNumMapPtr = std::make_shared<SensorNumMap>(); 244 245 uint16_t sensorNum = 0; 246 uint16_t sensorIndex = 0; 247 for (const auto& sensor : *sensorTree) 248 { 249 sensorNumMapPtr->insert( 250 SensorNumMap::value_type(sensorNum, sensor.first)); 251 sensorIndex++; 252 if (sensorIndex == maxSensorsPerLUN) 253 { 254 sensorIndex = lun1Sensor0; 255 } 256 else if (sensorIndex == (lun1Sensor0 | maxSensorsPerLUN)) 257 { 258 // Skip assigning LUN 0x2 any sensors 259 sensorIndex = lun3Sensor0; 260 } 261 else if (sensorIndex == (lun3Sensor0 | maxSensorsPerLUN)) 262 { 263 // this is an error, too many IPMI sensors 264 throw std::out_of_range("Maximum number of IPMI sensors exceeded."); 265 } 266 sensorNum = sensorIndex; 267 } 268 sensorNumMap = sensorNumMapPtr; 269 sensorNumMapUpated = true; 270 return sensorNumMapUpated; 271 } 272 } // namespace details 273 274 bool getSensorSubtree(SensorSubTree& subtree) 275 { 276 std::shared_ptr<SensorSubTree> sensorTree; 277 details::getSensorSubtree(sensorTree); 278 if (!sensorTree) 279 { 280 return false; 281 } 282 283 subtree = *sensorTree; 284 return true; 285 } 286 287 #ifdef FEATURE_HYBRID_SENSORS 288 // Static sensors are listed in sensor-gen.cpp. 289 ipmi::sensor::IdInfoMap::const_iterator 290 findStaticSensor(const std::string& path) 291 { 292 return std::find_if( 293 ipmi::sensor::sensors.begin(), ipmi::sensor::sensors.end(), 294 [&path](const ipmi::sensor::IdInfoMap::value_type& findSensor) { 295 return findSensor.second.sensorPath == path; 296 }); 297 } 298 #endif 299 300 std::string getSensorTypeStringFromPath(const std::string& path) 301 { 302 // get sensor type string from path, path is defined as 303 // /xyz/openbmc_project/sensors/<type>/label 304 size_t typeEnd = path.rfind("/"); 305 if (typeEnd == std::string::npos) 306 { 307 return path; 308 } 309 size_t typeStart = path.rfind("/", typeEnd - 1); 310 if (typeStart == std::string::npos) 311 { 312 return path; 313 } 314 // Start at the character after the '/' 315 typeStart++; 316 return path.substr(typeStart, typeEnd - typeStart); 317 } 318 319 uint8_t getSensorTypeFromPath(const std::string& path) 320 { 321 uint8_t sensorType = 0; 322 std::string type = getSensorTypeStringFromPath(path); 323 auto findSensor = sensorTypes.find(type.c_str()); 324 if (findSensor != sensorTypes.end()) 325 { 326 sensorType = 327 static_cast<uint8_t>(std::get<sensorTypeCodes>(findSensor->second)); 328 } // else default 0x0 RESERVED 329 330 return sensorType; 331 } 332 333 uint16_t getSensorNumberFromPath(const std::string& path) 334 { 335 std::shared_ptr<SensorNumMap> sensorNumMapPtr; 336 details::getSensorNumMap(sensorNumMapPtr); 337 if (!sensorNumMapPtr) 338 { 339 return invalidSensorNumber; 340 } 341 342 try 343 { 344 return sensorNumMapPtr->right.at(path); 345 } 346 catch (const std::out_of_range& e) 347 { 348 return invalidSensorNumber; 349 } 350 } 351 352 uint8_t getSensorEventTypeFromPath(const std::string& path) 353 { 354 uint8_t sensorEventType = 0; 355 std::string type = getSensorTypeStringFromPath(path); 356 auto findSensor = sensorTypes.find(type.c_str()); 357 if (findSensor != sensorTypes.end()) 358 { 359 sensorEventType = static_cast<uint8_t>( 360 std::get<sensorEventTypeCodes>(findSensor->second)); 361 } 362 363 return sensorEventType; 364 } 365 366 std::string getPathFromSensorNumber(uint16_t sensorNum) 367 { 368 std::shared_ptr<SensorNumMap> sensorNumMapPtr; 369 details::getSensorNumMap(sensorNumMapPtr); 370 if (!sensorNumMapPtr) 371 { 372 return std::string(); 373 } 374 375 try 376 { 377 return sensorNumMapPtr->left.at(sensorNum); 378 } 379 catch (const std::out_of_range& e) 380 { 381 return std::string(); 382 } 383 } 384 385 namespace ipmi 386 { 387 388 std::optional<std::map<std::string, std::vector<std::string>>> 389 getObjectInterfaces(const char* path) 390 { 391 std::map<std::string, std::vector<std::string>> interfacesResponse; 392 std::vector<std::string> interfaces; 393 std::shared_ptr<sdbusplus::asio::connection> dbus = getSdBus(); 394 395 sdbusplus::message_t getObjectMessage = 396 dbus->new_method_call("xyz.openbmc_project.ObjectMapper", 397 "/xyz/openbmc_project/object_mapper", 398 "xyz.openbmc_project.ObjectMapper", "GetObject"); 399 getObjectMessage.append(path, interfaces); 400 401 try 402 { 403 sdbusplus::message_t response = dbus->call(getObjectMessage); 404 response.read(interfacesResponse); 405 } 406 catch (const std::exception& e) 407 { 408 return std::nullopt; 409 } 410 411 return interfacesResponse; 412 } 413 414 std::map<std::string, Value> getEntityManagerProperties(const char* path, 415 const char* interface) 416 { 417 std::map<std::string, Value> properties; 418 std::shared_ptr<sdbusplus::asio::connection> dbus = getSdBus(); 419 420 sdbusplus::message_t getProperties = 421 dbus->new_method_call("xyz.openbmc_project.EntityManager", path, 422 "org.freedesktop.DBus.Properties", "GetAll"); 423 getProperties.append(interface); 424 425 try 426 { 427 sdbusplus::message_t response = dbus->call(getProperties); 428 response.read(properties); 429 } 430 catch (const std::exception& e) 431 { 432 phosphor::logging::log<phosphor::logging::level::ERR>( 433 "Failed to GetAll", phosphor::logging::entry("PATH=%s", path), 434 phosphor::logging::entry("INTF=%s", interface), 435 phosphor::logging::entry("WHAT=%s", e.what())); 436 } 437 438 return properties; 439 } 440 441 // Fetch the ipmiDecoratorPaths to get the list of dbus objects that 442 // have ipmi decorator to prevent unnessary dbus call to fetch the info 443 std::optional<std::unordered_set<std::string>>& 444 getIpmiDecoratorPaths(const std::optional<ipmi::Context::ptr>& ctx) 445 { 446 static std::optional<std::unordered_set<std::string>> ipmiDecoratorPaths; 447 448 if (!ctx.has_value() || ipmiDecoratorPaths != std::nullopt) 449 { 450 return ipmiDecoratorPaths; 451 } 452 453 boost::system::error_code ec; 454 std::vector<std::string> paths = 455 (*ctx)->bus->yield_method_call<std::vector<std::string>>( 456 (*ctx)->yield, ec, "xyz.openbmc_project.ObjectMapper", 457 "/xyz/openbmc_project/object_mapper", 458 "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", "/", 459 int32_t(0), 460 std::array<const char*, 1>{ 461 "xyz.openbmc_project.Inventory.Decorator.Ipmi"}); 462 if (ec) 463 { 464 return ipmiDecoratorPaths; 465 } 466 467 ipmiDecoratorPaths = std::unordered_set<std::string>(paths.begin(), 468 paths.end()); 469 return ipmiDecoratorPaths; 470 } 471 472 const std::string* getSensorConfigurationInterface( 473 const std::map<std::string, std::vector<std::string>>& 474 sensorInterfacesResponse) 475 { 476 auto entityManagerService = 477 sensorInterfacesResponse.find("xyz.openbmc_project.EntityManager"); 478 if (entityManagerService == sensorInterfacesResponse.end()) 479 { 480 return nullptr; 481 } 482 483 // Find the fan configuration first (fans can have multiple configuration 484 // interfaces). 485 for (const auto& entry : entityManagerService->second) 486 { 487 if (entry == "xyz.openbmc_project.Configuration.AspeedFan" || 488 entry == "xyz.openbmc_project.Configuration.I2CFan" || 489 entry == "xyz.openbmc_project.Configuration.NuvotonFan") 490 { 491 return &entry; 492 } 493 } 494 495 for (const auto& entry : entityManagerService->second) 496 { 497 if (boost::algorithm::starts_with(entry, 498 "xyz.openbmc_project.Configuration.")) 499 { 500 return &entry; 501 } 502 } 503 504 return nullptr; 505 } 506 507 // Follow Association properties for Sensor back to the Board dbus object to 508 // check for an EntityId and EntityInstance property. 509 void updateIpmiFromAssociation( 510 const std::string& path, 511 const std::unordered_set<std::string>& ipmiDecoratorPaths, 512 const DbusInterfaceMap& sensorMap, uint8_t& entityId, 513 uint8_t& entityInstance) 514 { 515 namespace fs = std::filesystem; 516 517 auto sensorAssociationObject = 518 sensorMap.find("xyz.openbmc_project.Association.Definitions"); 519 if (sensorAssociationObject == sensorMap.end()) 520 { 521 if constexpr (debug) 522 { 523 std::fprintf(stderr, "path=%s, no association interface found\n", 524 path.c_str()); 525 } 526 527 return; 528 } 529 530 auto associationObject = 531 sensorAssociationObject->second.find("Associations"); 532 if (associationObject == sensorAssociationObject->second.end()) 533 { 534 if constexpr (debug) 535 { 536 std::fprintf(stderr, "path=%s, no association records found\n", 537 path.c_str()); 538 } 539 540 return; 541 } 542 543 std::vector<Association> associationValues = 544 std::get<std::vector<Association>>(associationObject->second); 545 546 // loop through the Associations looking for the right one: 547 for (const auto& entry : associationValues) 548 { 549 // forward, reverse, endpoint 550 const std::string& forward = std::get<0>(entry); 551 const std::string& reverse = std::get<1>(entry); 552 const std::string& endpoint = std::get<2>(entry); 553 554 // We only currently concern ourselves with chassis+all_sensors. 555 if (!(forward == "chassis" && reverse == "all_sensors")) 556 { 557 continue; 558 } 559 560 // the endpoint is the board entry provided by 561 // Entity-Manager. so let's grab its properties if it has 562 // the right interface. 563 564 // just try grabbing the properties first. 565 ipmi::PropertyMap::iterator entityIdProp; 566 ipmi::PropertyMap::iterator entityInstanceProp; 567 if (ipmiDecoratorPaths.contains(endpoint)) 568 { 569 std::map<std::string, Value> ipmiProperties = 570 getEntityManagerProperties( 571 endpoint.c_str(), 572 "xyz.openbmc_project.Inventory.Decorator.Ipmi"); 573 574 entityIdProp = ipmiProperties.find("EntityId"); 575 entityInstanceProp = ipmiProperties.find("EntityInstance"); 576 if (entityIdProp != ipmiProperties.end()) 577 { 578 entityId = static_cast<uint8_t>( 579 std::get<uint64_t>(entityIdProp->second)); 580 } 581 if (entityInstanceProp != ipmiProperties.end()) 582 { 583 entityInstance = static_cast<uint8_t>( 584 std::get<uint64_t>(entityInstanceProp->second)); 585 } 586 } 587 588 // Now check the entity-manager entry for this sensor to see 589 // if it has its own value and use that instead. 590 // 591 // In theory, checking this first saves us from checking 592 // both, except in most use-cases identified, there won't be 593 // a per sensor override, so we need to always check both. 594 std::string sensorNameFromPath = fs::path(path).filename(); 595 596 std::string sensorConfigPath = endpoint + "/" + sensorNameFromPath; 597 598 // Download the interfaces for the sensor from 599 // Entity-Manager to find the name of the configuration 600 // interface. 601 std::optional<std::map<std::string, std::vector<std::string>>> 602 sensorInterfacesResponseOpt = 603 getObjectInterfaces(sensorConfigPath.c_str()); 604 605 if (!sensorInterfacesResponseOpt.has_value()) 606 { 607 phosphor::logging::log<phosphor::logging::level::DEBUG>( 608 "Failed to GetObject", 609 phosphor::logging::entry("PATH=%s", sensorConfigPath.c_str())); 610 continue; 611 } 612 613 const std::string* configurationInterface = 614 getSensorConfigurationInterface( 615 sensorInterfacesResponseOpt.value()); 616 617 // If there are multi association path settings and only one path exist, 618 // we need to continue if cannot find configuration interface for this 619 // sensor. 620 if (!configurationInterface) 621 { 622 continue; 623 } 624 625 // We found a configuration interface. 626 std::map<std::string, Value> configurationProperties = 627 getEntityManagerProperties(sensorConfigPath.c_str(), 628 configurationInterface->c_str()); 629 630 entityIdProp = configurationProperties.find("EntityId"); 631 entityInstanceProp = configurationProperties.find("EntityInstance"); 632 if (entityIdProp != configurationProperties.end()) 633 { 634 entityId = 635 static_cast<uint8_t>(std::get<uint64_t>(entityIdProp->second)); 636 } 637 if (entityInstanceProp != configurationProperties.end()) 638 { 639 entityInstance = static_cast<uint8_t>( 640 std::get<uint64_t>(entityInstanceProp->second)); 641 } 642 643 // stop searching Association records. 644 break; 645 } // end for Association vectors. 646 647 if constexpr (debug) 648 { 649 std::fprintf(stderr, "path=%s, entityId=%d, entityInstance=%d\n", 650 path.c_str(), entityId, entityInstance); 651 } 652 } 653 654 } // namespace ipmi 655