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 19 #include <boost/algorithm/string.hpp> 20 #include <boost/bimap.hpp> 21 #include <boost/container/flat_map.hpp> 22 #include <cstdio> 23 #include <cstring> 24 #include <exception> 25 #include <filesystem> 26 #include <map> 27 #include <phosphor-logging/log.hpp> 28 #include <sdbusplus/bus/match.hpp> 29 #include <string> 30 #include <vector> 31 32 #pragma once 33 34 struct CmpStrVersion 35 { 36 bool operator()(std::string a, std::string b) const 37 { 38 return strverscmp(a.c_str(), b.c_str()) < 0; 39 } 40 }; 41 42 using SensorSubTree = boost::container::flat_map< 43 std::string, 44 boost::container::flat_map<std::string, std::vector<std::string>>, 45 CmpStrVersion>; 46 47 using SensorNumMap = boost::bimap<int, std::string>; 48 49 namespace details 50 { 51 inline static bool getSensorSubtree(std::shared_ptr<SensorSubTree>& subtree) 52 { 53 static std::shared_ptr<SensorSubTree> sensorTreePtr; 54 sd_bus* bus = NULL; 55 int ret = sd_bus_default_system(&bus); 56 if (ret < 0) 57 { 58 phosphor::logging::log<phosphor::logging::level::ERR>( 59 "Failed to connect to system bus", 60 phosphor::logging::entry("ERRNO=0x%X", -ret)); 61 sd_bus_unref(bus); 62 return false; 63 } 64 sdbusplus::bus::bus dbus(bus); 65 static sdbusplus::bus::match::match sensorAdded( 66 dbus, 67 "type='signal',member='InterfacesAdded',arg0path='/xyz/openbmc_project/" 68 "sensors/'", 69 [](sdbusplus::message::message& m) { sensorTreePtr.reset(); }); 70 71 static sdbusplus::bus::match::match sensorRemoved( 72 dbus, 73 "type='signal',member='InterfacesRemoved',arg0path='/xyz/" 74 "openbmc_project/sensors/'", 75 [](sdbusplus::message::message& m) { sensorTreePtr.reset(); }); 76 77 bool sensorTreeUpdated = false; 78 if (sensorTreePtr) 79 { 80 subtree = sensorTreePtr; 81 return sensorTreeUpdated; 82 } 83 84 sensorTreePtr = std::make_shared<SensorSubTree>(); 85 86 auto mapperCall = 87 dbus.new_method_call("xyz.openbmc_project.ObjectMapper", 88 "/xyz/openbmc_project/object_mapper", 89 "xyz.openbmc_project.ObjectMapper", "GetSubTree"); 90 static constexpr const auto depth = 2; 91 static constexpr std::array<const char*, 3> interfaces = { 92 "xyz.openbmc_project.Sensor.Value", 93 "xyz.openbmc_project.Sensor.Threshold.Warning", 94 "xyz.openbmc_project.Sensor.Threshold.Critical"}; 95 mapperCall.append("/xyz/openbmc_project/sensors", depth, interfaces); 96 97 try 98 { 99 auto mapperReply = dbus.call(mapperCall); 100 mapperReply.read(*sensorTreePtr); 101 } 102 catch (sdbusplus::exception_t& e) 103 { 104 phosphor::logging::log<phosphor::logging::level::ERR>(e.what()); 105 return sensorTreeUpdated; 106 } 107 subtree = sensorTreePtr; 108 sensorTreeUpdated = true; 109 return sensorTreeUpdated; 110 } 111 112 inline static bool getSensorNumMap(std::shared_ptr<SensorNumMap>& sensorNumMap) 113 { 114 static std::shared_ptr<SensorNumMap> sensorNumMapPtr; 115 bool sensorNumMapUpated = false; 116 117 std::shared_ptr<SensorSubTree> sensorTree; 118 bool sensorTreeUpdated = details::getSensorSubtree(sensorTree); 119 if (!sensorTree) 120 { 121 return sensorNumMapUpated; 122 } 123 124 if (!sensorTreeUpdated && sensorNumMapPtr) 125 { 126 sensorNumMap = sensorNumMapPtr; 127 return sensorNumMapUpated; 128 } 129 130 sensorNumMapPtr = std::make_shared<SensorNumMap>(); 131 132 uint8_t sensorNum = 0; 133 for (const auto& sensor : *sensorTree) 134 { 135 sensorNumMapPtr->insert( 136 SensorNumMap::value_type(sensorNum++, sensor.first)); 137 } 138 sensorNumMap = sensorNumMapPtr; 139 sensorNumMapUpated = true; 140 return sensorNumMapUpated; 141 } 142 } // namespace details 143 144 inline static bool getSensorSubtree(SensorSubTree& subtree) 145 { 146 std::shared_ptr<SensorSubTree> sensorTree; 147 details::getSensorSubtree(sensorTree); 148 if (!sensorTree) 149 { 150 return false; 151 } 152 153 subtree = *sensorTree; 154 return true; 155 } 156 157 struct CmpStr 158 { 159 bool operator()(const char* a, const char* b) const 160 { 161 return std::strcmp(a, b) < 0; 162 } 163 }; 164 165 enum class SensorTypeCodes : uint8_t 166 { 167 reserved = 0x0, 168 temperature = 0x1, 169 voltage = 0x2, 170 current = 0x3, 171 fan = 0x4, 172 other = 0xB, 173 }; 174 175 const static boost::container::flat_map<const char*, SensorTypeCodes, CmpStr> 176 sensorTypes{{{"temperature", SensorTypeCodes::temperature}, 177 {"voltage", SensorTypeCodes::voltage}, 178 {"current", SensorTypeCodes::current}, 179 {"fan_tach", SensorTypeCodes::fan}, 180 {"fan_pwm", SensorTypeCodes::fan}, 181 {"power", SensorTypeCodes::other}}}; 182 183 inline static std::string getSensorTypeStringFromPath(const std::string& path) 184 { 185 // get sensor type string from path, path is defined as 186 // /xyz/openbmc_project/sensors/<type>/label 187 size_t typeEnd = path.rfind("/"); 188 if (typeEnd == std::string::npos) 189 { 190 return path; 191 } 192 size_t typeStart = path.rfind("/", typeEnd - 1); 193 if (typeStart == std::string::npos) 194 { 195 return path; 196 } 197 // Start at the character after the '/' 198 typeStart++; 199 return path.substr(typeStart, typeEnd - typeStart); 200 } 201 202 inline static uint8_t getSensorTypeFromPath(const std::string& path) 203 { 204 uint8_t sensorType = 0; 205 std::string type = getSensorTypeStringFromPath(path); 206 auto findSensor = sensorTypes.find(type.c_str()); 207 if (findSensor != sensorTypes.end()) 208 { 209 sensorType = static_cast<uint8_t>(findSensor->second); 210 } // else default 0x0 RESERVED 211 212 return sensorType; 213 } 214 215 inline static uint8_t getSensorNumberFromPath(const std::string& path) 216 { 217 std::shared_ptr<SensorNumMap> sensorNumMapPtr; 218 details::getSensorNumMap(sensorNumMapPtr); 219 if (!sensorNumMapPtr) 220 { 221 return 0xFF; 222 } 223 224 try 225 { 226 return sensorNumMapPtr->right.at(path); 227 } 228 catch (std::out_of_range& e) 229 { 230 phosphor::logging::log<phosphor::logging::level::ERR>(e.what()); 231 return 0xFF; 232 } 233 } 234 235 inline static uint8_t getSensorEventTypeFromPath(const std::string& path) 236 { 237 // TODO: Add support for additional reading types as needed 238 return 0x1; // reading type = threshold 239 } 240 241 inline static std::string getPathFromSensorNumber(uint8_t sensorNum) 242 { 243 std::shared_ptr<SensorNumMap> sensorNumMapPtr; 244 details::getSensorNumMap(sensorNumMapPtr); 245 if (!sensorNumMapPtr) 246 { 247 return std::string(); 248 } 249 250 try 251 { 252 return sensorNumMapPtr->left.at(sensorNum); 253 } 254 catch (std::out_of_range& e) 255 { 256 phosphor::logging::log<phosphor::logging::level::ERR>(e.what()); 257 return std::string(); 258 } 259 } 260 261 namespace ipmi 262 { 263 264 static inline std::map<std::string, std::vector<std::string>> 265 getObjectInterfaces(const char* path) 266 { 267 std::map<std::string, std::vector<std::string>> interfacesResponse; 268 std::vector<std::string> interfaces; 269 std::shared_ptr<sdbusplus::asio::connection> dbus = getSdBus(); 270 271 sdbusplus::message::message getObjectMessage = 272 dbus->new_method_call("xyz.openbmc_project.ObjectMapper", 273 "/xyz/openbmc_project/object_mapper", 274 "xyz.openbmc_project.ObjectMapper", "GetObject"); 275 getObjectMessage.append(path, interfaces); 276 277 try 278 { 279 sdbusplus::message::message response = dbus->call(getObjectMessage); 280 response.read(interfacesResponse); 281 } 282 catch (const std::exception& e) 283 { 284 phosphor::logging::log<phosphor::logging::level::ERR>( 285 "Failed to GetObject", phosphor::logging::entry("PATH=%s", path), 286 phosphor::logging::entry("WHAT=%s", e.what())); 287 } 288 289 return interfacesResponse; 290 } 291 292 static inline std::map<std::string, DbusVariant> 293 getEntityManagerProperties(const char* path, const char* interface) 294 { 295 std::map<std::string, DbusVariant> properties; 296 std::shared_ptr<sdbusplus::asio::connection> dbus = getSdBus(); 297 298 sdbusplus::message::message getProperties = 299 dbus->new_method_call("xyz.openbmc_project.EntityManager", path, 300 "org.freedesktop.DBus.Properties", "GetAll"); 301 getProperties.append(interface); 302 303 try 304 { 305 sdbusplus::message::message response = dbus->call(getProperties); 306 response.read(properties); 307 } 308 catch (const std::exception& e) 309 { 310 phosphor::logging::log<phosphor::logging::level::ERR>( 311 "Failed to GetAll", phosphor::logging::entry("PATH=%s", path), 312 phosphor::logging::entry("INTF=%s", interface), 313 phosphor::logging::entry("WHAT=%s", e.what())); 314 } 315 316 return properties; 317 } 318 319 static inline const std::string* getSensorConfigurationInterface( 320 const std::map<std::string, std::vector<std::string>>& 321 sensorInterfacesResponse) 322 { 323 auto entityManagerService = 324 sensorInterfacesResponse.find("xyz.openbmc_project.EntityManager"); 325 if (entityManagerService == sensorInterfacesResponse.end()) 326 { 327 return nullptr; 328 } 329 330 // Find the fan configuration first (fans can have multiple configuration 331 // interfaces). 332 for (const auto& entry : entityManagerService->second) 333 { 334 if (entry == "xyz.openbmc_project.Configuration.AspeedFan" || 335 entry == "xyz.openbmc_project.Configuration.I2CFan" || 336 entry == "xyz.openbmc_project.Configuration.NuvotonFan") 337 { 338 return &entry; 339 } 340 } 341 342 for (const auto& entry : entityManagerService->second) 343 { 344 if (boost::algorithm::starts_with(entry, 345 "xyz.openbmc_project.Configuration.")) 346 { 347 return &entry; 348 } 349 } 350 351 return nullptr; 352 } 353 354 // Follow Association properties for Sensor back to the Board dbus object to 355 // check for an EntityId and EntityInstance property. 356 static inline void updateIpmiFromAssociation(const std::string& path, 357 const SensorMap& sensorMap, 358 uint8_t& entityId, 359 uint8_t& entityInstance) 360 { 361 namespace fs = std::filesystem; 362 363 auto sensorAssociationObject = 364 sensorMap.find("xyz.openbmc_project.Association.Definitions"); 365 if (sensorAssociationObject == sensorMap.end()) 366 { 367 if constexpr (debug) 368 { 369 std::fprintf(stderr, "path=%s, no association interface found\n", 370 path.c_str()); 371 } 372 373 return; 374 } 375 376 auto associationObject = 377 sensorAssociationObject->second.find("Associations"); 378 if (associationObject == sensorAssociationObject->second.end()) 379 { 380 if constexpr (debug) 381 { 382 std::fprintf(stderr, "path=%s, no association records found\n", 383 path.c_str()); 384 } 385 386 return; 387 } 388 389 std::vector<Association> associationValues = 390 std::get<std::vector<Association>>(associationObject->second); 391 392 // loop through the Associations looking for the right one: 393 for (const auto& entry : associationValues) 394 { 395 // forward, reverse, endpoint 396 const std::string& forward = std::get<0>(entry); 397 const std::string& reverse = std::get<1>(entry); 398 const std::string& endpoint = std::get<2>(entry); 399 400 // We only currently concern ourselves with chassis+all_sensors. 401 if (!(forward == "chassis" && reverse == "all_sensors")) 402 { 403 continue; 404 } 405 406 // the endpoint is the board entry provided by 407 // Entity-Manager. so let's grab its properties if it has 408 // the right interface. 409 410 // just try grabbing the properties first. 411 std::map<std::string, DbusVariant> ipmiProperties = 412 getEntityManagerProperties( 413 endpoint.c_str(), 414 "xyz.openbmc_project.Inventory.Decorator.Ipmi"); 415 416 auto entityIdProp = ipmiProperties.find("EntityId"); 417 auto entityInstanceProp = ipmiProperties.find("EntityInstance"); 418 if (entityIdProp != ipmiProperties.end()) 419 { 420 entityId = 421 static_cast<uint8_t>(std::get<uint64_t>(entityIdProp->second)); 422 } 423 if (entityInstanceProp != ipmiProperties.end()) 424 { 425 entityInstance = static_cast<uint8_t>( 426 std::get<uint64_t>(entityInstanceProp->second)); 427 } 428 429 // Now check the entity-manager entry for this sensor to see 430 // if it has its own value and use that instead. 431 // 432 // In theory, checking this first saves us from checking 433 // both, except in most use-cases identified, there won't be 434 // a per sensor override, so we need to always check both. 435 std::string sensorNameFromPath = fs::path(path).filename(); 436 437 std::string sensorConfigPath = endpoint + "/" + sensorNameFromPath; 438 439 // Download the interfaces for the sensor from 440 // Entity-Manager to find the name of the configuration 441 // interface. 442 std::map<std::string, std::vector<std::string>> 443 sensorInterfacesResponse = 444 getObjectInterfaces(sensorConfigPath.c_str()); 445 446 const std::string* configurationInterface = 447 getSensorConfigurationInterface(sensorInterfacesResponse); 448 449 // We didnt' find a configuration interface for this sensor, but we 450 // followed the Association property to get here, so we're done 451 // searching. 452 if (!configurationInterface) 453 { 454 break; 455 } 456 457 // We found a configuration interface. 458 std::map<std::string, DbusVariant> configurationProperties = 459 getEntityManagerProperties(sensorConfigPath.c_str(), 460 configurationInterface->c_str()); 461 462 entityIdProp = configurationProperties.find("EntityId"); 463 entityInstanceProp = configurationProperties.find("EntityInstance"); 464 if (entityIdProp != configurationProperties.end()) 465 { 466 entityId = 467 static_cast<uint8_t>(std::get<uint64_t>(entityIdProp->second)); 468 } 469 if (entityInstanceProp != configurationProperties.end()) 470 { 471 entityInstance = static_cast<uint8_t>( 472 std::get<uint64_t>(entityInstanceProp->second)); 473 } 474 475 // stop searching Association records. 476 break; 477 } // end for Association vectors. 478 479 if constexpr (debug) 480 { 481 std::fprintf(stderr, "path=%s, entityId=%d, entityInstance=%d\n", 482 path.c_str(), entityId, entityInstance); 483 } 484 } 485 486 } // namespace ipmi 487