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