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