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