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