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