1 /** 2 * Copyright © 2019 IBM 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 #include "config.h" 17 18 #include "data_interface.hpp" 19 20 #include "util.hpp" 21 22 #include <fmt/format.h> 23 24 #include <fstream> 25 #include <phosphor-logging/log.hpp> 26 #include <xyz/openbmc_project/State/Boot/Progress/server.hpp> 27 28 namespace openpower 29 { 30 namespace pels 31 { 32 33 namespace service_name 34 { 35 constexpr auto objectMapper = "xyz.openbmc_project.ObjectMapper"; 36 constexpr auto vpdManager = "com.ibm.VPD.Manager"; 37 constexpr auto ledGroupManager = "xyz.openbmc_project.LED.GroupManager"; 38 constexpr auto logSetting = "xyz.openbmc_project.Settings"; 39 } // namespace service_name 40 41 namespace object_path 42 { 43 constexpr auto objectMapper = "/xyz/openbmc_project/object_mapper"; 44 constexpr auto systemInv = "/xyz/openbmc_project/inventory/system"; 45 constexpr auto chassisInv = "/xyz/openbmc_project/inventory/system/chassis"; 46 constexpr auto motherBoardInv = 47 "/xyz/openbmc_project/inventory/system/chassis/motherboard"; 48 constexpr auto baseInv = "/xyz/openbmc_project/inventory"; 49 constexpr auto bmcState = "/xyz/openbmc_project/state/bmc0"; 50 constexpr auto chassisState = "/xyz/openbmc_project/state/chassis0"; 51 constexpr auto hostState = "/xyz/openbmc_project/state/host0"; 52 constexpr auto pldm = "/xyz/openbmc_project/pldm"; 53 constexpr auto enableHostPELs = 54 "/xyz/openbmc_project/logging/send_event_logs_to_host"; 55 constexpr auto vpdManager = "/com/ibm/VPD/Manager"; 56 constexpr auto logSetting = "/xyz/openbmc_project/logging/settings"; 57 } // namespace object_path 58 59 namespace interface 60 { 61 constexpr auto dbusProperty = "org.freedesktop.DBus.Properties"; 62 constexpr auto objectMapper = "xyz.openbmc_project.ObjectMapper"; 63 constexpr auto invAsset = "xyz.openbmc_project.Inventory.Decorator.Asset"; 64 constexpr auto bootProgress = "xyz.openbmc_project.State.Boot.Progress"; 65 constexpr auto pldmRequester = "xyz.openbmc_project.PLDM.Requester"; 66 constexpr auto enable = "xyz.openbmc_project.Object.Enable"; 67 constexpr auto bmcState = "xyz.openbmc_project.State.BMC"; 68 constexpr auto chassisState = "xyz.openbmc_project.State.Chassis"; 69 constexpr auto hostState = "xyz.openbmc_project.State.Host"; 70 constexpr auto invMotherboard = 71 "xyz.openbmc_project.Inventory.Item.Board.Motherboard"; 72 constexpr auto viniRecordVPD = "com.ibm.ipzvpd.VINI"; 73 constexpr auto vsbpRecordVPD = "com.ibm.ipzvpd.VSBP"; 74 constexpr auto locCode = "com.ibm.ipzvpd.Location"; 75 constexpr auto compatible = 76 "xyz.openbmc_project.Configuration.IBMCompatibleSystem"; 77 constexpr auto vpdManager = "com.ibm.VPD.Manager"; 78 constexpr auto ledGroup = "xyz.openbmc_project.Led.Group"; 79 constexpr auto operationalStatus = 80 "xyz.openbmc_project.State.Decorator.OperationalStatus"; 81 constexpr auto logSetting = "xyz.openbmc_project.Logging.Settings"; 82 } // namespace interface 83 84 using namespace sdbusplus::xyz::openbmc_project::State::Boot::server; 85 using sdbusplus::exception::SdBusError; 86 using namespace phosphor::logging; 87 88 std::pair<std::string, std::string> 89 DataInterfaceBase::extractConnectorFromLocCode( 90 const std::string& locationCode) 91 { 92 auto base = locationCode; 93 std::string connector{}; 94 95 auto pos = base.find("-T"); 96 if (pos != std::string::npos) 97 { 98 connector = base.substr(pos); 99 base = base.substr(0, pos); 100 } 101 102 return {base, connector}; 103 } 104 105 DataInterface::DataInterface(sdbusplus::bus::bus& bus) : _bus(bus) 106 { 107 readBMCFWVersion(); 108 readServerFWVersion(); 109 readBMCFWVersionID(); 110 111 // Watch the BootProgress property 112 _properties.emplace_back(std::make_unique<PropertyWatcher<DataInterface>>( 113 bus, object_path::hostState, interface::bootProgress, "BootProgress", 114 *this, [this](const auto& value) { 115 auto status = Progress::convertProgressStagesFromString( 116 std::get<std::string>(value)); 117 118 if ((status == Progress::ProgressStages::SystemInitComplete) || 119 (status == Progress::ProgressStages::OSStart) || 120 (status == Progress::ProgressStages::OSRunning)) 121 { 122 setHostUp(true); 123 } 124 else 125 { 126 setHostUp(false); 127 } 128 })); 129 130 // Watch the host PEL enable property 131 _properties.emplace_back(std::make_unique<PropertyWatcher<DataInterface>>( 132 bus, object_path::enableHostPELs, interface::enable, "Enabled", *this, 133 [this](const auto& value) { 134 this->_sendPELsToHost = std::get<bool>(value); 135 })); 136 137 // Watch the BMCState property 138 _properties.emplace_back(std::make_unique<PropertyWatcher<DataInterface>>( 139 bus, object_path::bmcState, interface::bmcState, "CurrentBMCState", 140 *this, [this](const auto& value) { 141 this->_bmcState = std::get<std::string>(value); 142 })); 143 144 // Watch the chassis current and requested power state properties 145 _properties.emplace_back(std::make_unique<InterfaceWatcher<DataInterface>>( 146 bus, object_path::chassisState, interface::chassisState, *this, 147 [this](const auto& properties) { 148 auto state = properties.find("CurrentPowerState"); 149 if (state != properties.end()) 150 { 151 this->_chassisState = std::get<std::string>(state->second); 152 } 153 154 auto trans = properties.find("RequestedPowerTransition"); 155 if (trans != properties.end()) 156 { 157 this->_chassisTransition = std::get<std::string>(trans->second); 158 } 159 })); 160 161 // Watch the CurrentHostState property 162 _properties.emplace_back(std::make_unique<PropertyWatcher<DataInterface>>( 163 bus, object_path::hostState, interface::hostState, "CurrentHostState", 164 *this, [this](const auto& value) { 165 this->_hostState = std::get<std::string>(value); 166 })); 167 } 168 169 DBusPropertyMap 170 DataInterface::getAllProperties(const std::string& service, 171 const std::string& objectPath, 172 const std::string& interface) const 173 { 174 DBusPropertyMap properties; 175 176 auto method = _bus.new_method_call(service.c_str(), objectPath.c_str(), 177 interface::dbusProperty, "GetAll"); 178 method.append(interface); 179 auto reply = _bus.call(method); 180 181 reply.read(properties); 182 183 return properties; 184 } 185 186 void DataInterface::getProperty(const std::string& service, 187 const std::string& objectPath, 188 const std::string& interface, 189 const std::string& property, 190 DBusValue& value) const 191 { 192 193 auto method = _bus.new_method_call(service.c_str(), objectPath.c_str(), 194 interface::dbusProperty, "Get"); 195 method.append(interface, property); 196 auto reply = _bus.call(method); 197 198 reply.read(value); 199 } 200 201 DBusPathList DataInterface::getPaths(const DBusInterfaceList& interfaces) const 202 { 203 204 auto method = _bus.new_method_call( 205 service_name::objectMapper, object_path::objectMapper, 206 interface::objectMapper, "GetSubTreePaths"); 207 208 method.append(std::string{"/"}, 0, interfaces); 209 210 auto reply = _bus.call(method); 211 212 DBusPathList paths; 213 reply.read(paths); 214 215 return paths; 216 } 217 218 DBusService DataInterface::getService(const std::string& objectPath, 219 const std::string& interface) const 220 { 221 auto method = _bus.new_method_call(service_name::objectMapper, 222 object_path::objectMapper, 223 interface::objectMapper, "GetObject"); 224 225 method.append(objectPath, std::vector<std::string>({interface})); 226 227 auto reply = _bus.call(method); 228 229 std::map<DBusService, DBusInterfaceList> response; 230 reply.read(response); 231 232 if (!response.empty()) 233 { 234 return response.begin()->first; 235 } 236 237 return std::string{}; 238 } 239 240 void DataInterface::readBMCFWVersion() 241 { 242 _bmcFWVersion = 243 phosphor::logging::util::getOSReleaseValue("VERSION").value_or(""); 244 } 245 246 void DataInterface::readServerFWVersion() 247 { 248 auto value = 249 phosphor::logging::util::getOSReleaseValue("VERSION_ID").value_or(""); 250 if ((value != "") && (value.find_last_of(')') != std::string::npos)) 251 { 252 std::size_t pos = value.find_first_of('(') + 1; 253 _serverFWVersion = value.substr(pos, value.find_last_of(')') - pos); 254 } 255 } 256 257 void DataInterface::readBMCFWVersionID() 258 { 259 _bmcFWVersionID = 260 phosphor::logging::util::getOSReleaseValue("VERSION_ID").value_or(""); 261 } 262 263 std::string DataInterface::getMachineTypeModel() const 264 { 265 std::string model; 266 try 267 { 268 269 auto service = getService(object_path::systemInv, interface::invAsset); 270 if (!service.empty()) 271 { 272 DBusValue value; 273 getProperty(service, object_path::systemInv, interface::invAsset, 274 "Model", value); 275 276 model = std::get<std::string>(value); 277 } 278 } 279 catch (const std::exception& e) 280 { 281 log<level::WARNING>(fmt::format("Failed reading Model property from " 282 "Interface: {} exception: {}", 283 interface::invAsset, e.what()) 284 .c_str()); 285 } 286 287 return model; 288 } 289 290 std::string DataInterface::getMachineSerialNumber() const 291 { 292 std::string sn; 293 try 294 { 295 296 auto service = getService(object_path::systemInv, interface::invAsset); 297 if (!service.empty()) 298 { 299 DBusValue value; 300 getProperty(service, object_path::systemInv, interface::invAsset, 301 "SerialNumber", value); 302 303 sn = std::get<std::string>(value); 304 } 305 } 306 catch (const std::exception& e) 307 { 308 log<level::WARNING>( 309 fmt::format("Failed reading SerialNumber property from " 310 "Interface: {} exception: {}", 311 interface::invAsset, e.what()) 312 .c_str()); 313 } 314 315 return sn; 316 } 317 318 std::string DataInterface::getMotherboardCCIN() const 319 { 320 std::string ccin; 321 322 try 323 { 324 auto service = 325 getService(object_path::motherBoardInv, interface::viniRecordVPD); 326 if (!service.empty()) 327 { 328 DBusValue value; 329 getProperty(service, object_path::motherBoardInv, 330 interface::viniRecordVPD, "CC", value); 331 332 auto cc = std::get<std::vector<uint8_t>>(value); 333 ccin = std::string{cc.begin(), cc.end()}; 334 } 335 } 336 catch (const std::exception& e) 337 { 338 log<level::WARNING>( 339 fmt::format("Failed reading Motherboard CCIN property from " 340 "Interface: {} exception: {}", 341 interface::viniRecordVPD, e.what()) 342 .c_str()); 343 } 344 345 return ccin; 346 } 347 348 std::vector<uint8_t> DataInterface::getSystemIMKeyword() const 349 { 350 std::vector<uint8_t> systemIM; 351 352 try 353 { 354 auto service = 355 getService(object_path::motherBoardInv, interface::vsbpRecordVPD); 356 if (!service.empty()) 357 { 358 DBusValue value; 359 getProperty(service, object_path::motherBoardInv, 360 interface::vsbpRecordVPD, "IM", value); 361 362 systemIM = std::get<std::vector<uint8_t>>(value); 363 } 364 } 365 catch (const std::exception& e) 366 { 367 log<level::WARNING>( 368 fmt::format("Failed reading System IM property from " 369 "Interface: {} exception: {}", 370 interface::vsbpRecordVPD, e.what()) 371 .c_str()); 372 } 373 374 return systemIM; 375 } 376 377 void DataInterface::getHWCalloutFields(const std::string& inventoryPath, 378 std::string& fruPartNumber, 379 std::string& ccin, 380 std::string& serialNumber) const 381 { 382 // For now, attempt to get all of the properties directly on the path 383 // passed in. In the future, may need to make use of an algorithm 384 // to figure out which inventory objects actually hold these 385 // interfaces in the case of non FRUs, or possibly another service 386 // will provide this info. Any missing interfaces will result 387 // in exceptions being thrown. 388 389 auto service = getService(inventoryPath, interface::viniRecordVPD); 390 391 auto properties = 392 getAllProperties(service, inventoryPath, interface::viniRecordVPD); 393 394 auto value = std::get<std::vector<uint8_t>>(properties["FN"]); 395 fruPartNumber = std::string{value.begin(), value.end()}; 396 397 value = std::get<std::vector<uint8_t>>(properties["CC"]); 398 ccin = std::string{value.begin(), value.end()}; 399 400 value = std::get<std::vector<uint8_t>>(properties["SN"]); 401 serialNumber = std::string{value.begin(), value.end()}; 402 } 403 404 std::string 405 DataInterface::getLocationCode(const std::string& inventoryPath) const 406 { 407 auto service = getService(inventoryPath, interface::locCode); 408 409 DBusValue locCode; 410 getProperty(service, inventoryPath, interface::locCode, "LocationCode", 411 locCode); 412 413 return std::get<std::string>(locCode); 414 } 415 416 std::string 417 DataInterface::addLocationCodePrefix(const std::string& locationCode) 418 { 419 static const std::string locationCodePrefix{"Ufcs-"}; 420 421 // Technically there are 2 location code prefixes, Ufcs and Umts, so 422 // if it already starts with a U then don't need to do anything. 423 if (locationCode.front() != 'U') 424 { 425 return locationCodePrefix + locationCode; 426 } 427 428 return locationCode; 429 } 430 431 std::string DataInterface::expandLocationCode(const std::string& locationCode, 432 uint16_t /*node*/) const 433 { 434 // Location codes for connectors are the location code of the FRU they are 435 // on, plus a '-Tx' segment. Remove this last segment before expanding it 436 // and then add it back in afterwards. This way, the connector doesn't have 437 // to be in the model just so that it can be expanded. 438 auto [baseLoc, connectorLoc] = extractConnectorFromLocCode(locationCode); 439 440 auto method = 441 _bus.new_method_call(service_name::vpdManager, object_path::vpdManager, 442 interface::vpdManager, "GetExpandedLocationCode"); 443 444 method.append(addLocationCodePrefix(baseLoc), static_cast<uint16_t>(0)); 445 446 auto reply = _bus.call(method); 447 448 std::string expandedLocationCode; 449 reply.read(expandedLocationCode); 450 451 if (!connectorLoc.empty()) 452 { 453 expandedLocationCode += connectorLoc; 454 } 455 456 return expandedLocationCode; 457 } 458 459 std::string 460 DataInterface::getInventoryFromLocCode(const std::string& locationCode, 461 uint16_t node, bool expanded) const 462 { 463 std::string methodName = expanded ? "GetFRUsByExpandedLocationCode" 464 : "GetFRUsByUnexpandedLocationCode"; 465 466 // Remove the connector segment, if present, so that this method call 467 // returns an inventory path that getHWCalloutFields() can be used with. 468 // (The serial number, etc, aren't stored on the connector in the 469 // inventory, and may not even be modeled.) 470 auto [baseLoc, connectorLoc] = extractConnectorFromLocCode(locationCode); 471 472 auto method = 473 _bus.new_method_call(service_name::vpdManager, object_path::vpdManager, 474 interface::vpdManager, methodName.c_str()); 475 476 if (expanded) 477 { 478 method.append(baseLoc); 479 } 480 else 481 { 482 method.append(addLocationCodePrefix(baseLoc), node); 483 } 484 485 auto reply = _bus.call(method); 486 487 std::vector<sdbusplus::message::object_path> entries; 488 reply.read(entries); 489 490 // Get the shortest entry from the paths received, as this 491 // would be the path furthest up the inventory hierarchy so 492 // would be the parent FRU. There is guaranteed to at least 493 // be one entry if the call didn't fail. 494 std::string shortest{entries[0]}; 495 496 std::for_each(entries.begin(), entries.end(), 497 [&shortest](const auto& path) { 498 if (path.str.size() < shortest.size()) 499 { 500 shortest = path; 501 } 502 }); 503 504 return shortest; 505 } 506 507 void DataInterface::assertLEDGroup(const std::string& ledGroup, 508 bool value) const 509 { 510 DBusValue variant = value; 511 auto method = 512 _bus.new_method_call(service_name::ledGroupManager, ledGroup.c_str(), 513 interface::dbusProperty, "Set"); 514 method.append(interface::ledGroup, "Asserted", variant); 515 _bus.call(method); 516 } 517 518 void DataInterface::setFunctional(const std::string& objectPath, 519 bool value) const 520 { 521 DBusValue variant = value; 522 auto service = getService(objectPath, interface::operationalStatus); 523 524 auto method = _bus.new_method_call(service.c_str(), objectPath.c_str(), 525 interface::dbusProperty, "Set"); 526 527 method.append(interface::operationalStatus, "Functional", variant); 528 _bus.call(method); 529 } 530 531 std::vector<std::string> DataInterface::getSystemNames() const 532 { 533 DBusSubTree subtree; 534 DBusValue names; 535 536 auto method = _bus.new_method_call(service_name::objectMapper, 537 object_path::objectMapper, 538 interface::objectMapper, "GetSubTree"); 539 method.append(std::string{"/"}, 0, 540 std::vector<std::string>{interface::compatible}); 541 auto reply = _bus.call(method); 542 543 reply.read(subtree); 544 if (subtree.empty()) 545 { 546 throw std::runtime_error("Compatible interface not on D-Bus"); 547 } 548 549 const auto& object = *(subtree.begin()); 550 const auto& path = object.first; 551 const auto& service = object.second.begin()->first; 552 553 getProperty(service, path, interface::compatible, "Names", names); 554 555 return std::get<std::vector<std::string>>(names); 556 } 557 558 bool DataInterface::getQuiesceOnError() const 559 { 560 bool ret = false; 561 562 try 563 { 564 auto service = 565 getService(object_path::logSetting, interface::logSetting); 566 if (!service.empty()) 567 { 568 DBusValue value; 569 getProperty(service, object_path::logSetting, interface::logSetting, 570 "QuiesceOnHwError", value); 571 572 ret = std::get<bool>(value); 573 } 574 } 575 catch (const std::exception& e) 576 { 577 log<level::WARNING>( 578 fmt::format("Failed reading QuiesceOnHwError property from " 579 "Interface: {} exception: {}", 580 interface::logSetting, e.what()) 581 .c_str()); 582 } 583 584 return ret; 585 } 586 587 } // namespace pels 588 } // namespace openpower 589