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