1 // Copyright 2022 Google LLC 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 #include "handler.hpp" 15 16 #include "bm_config.h" 17 18 #include "bmc_mode_enum.hpp" 19 #include "errors.hpp" 20 #include "handler_impl.hpp" 21 #include "util.hpp" 22 23 #include <fcntl.h> 24 #include <ipmid/api.h> 25 #include <mtd/mtd-abi.h> 26 #include <mtd/mtd-user.h> 27 #include <sys/ioctl.h> 28 #include <unistd.h> 29 30 #include <nlohmann/json.hpp> 31 #include <phosphor-logging/elog-errors.hpp> 32 #include <phosphor-logging/log.hpp> 33 #include <sdbusplus/bus.hpp> 34 #include <xyz/openbmc_project/Common/error.hpp> 35 36 #include <cinttypes> 37 #include <cstdio> 38 #include <filesystem> 39 #include <fstream> 40 #include <map> 41 #include <sstream> 42 #include <string> 43 #include <string_view> 44 #include <tuple> 45 #include <variant> 46 47 #ifndef NCSI_IF_NAME 48 #define NCSI_IF_NAME eth0 49 #endif 50 51 // To deal with receiving a string without quotes. 52 #define QUOTE(name) #name 53 #define STR(macro) QUOTE(macro) 54 #define NCSI_IF_NAME_STR STR(NCSI_IF_NAME) 55 56 namespace ipmi 57 { 58 std::uint8_t getChannelByName(const std::string& chName); 59 } 60 61 namespace google 62 { 63 namespace ipmi 64 { 65 using Json = nlohmann::json; 66 using namespace phosphor::logging; 67 using InternalFailure = 68 sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure; 69 70 uint8_t isBmcInBareMetalMode(const std::unique_ptr<FileSystemInterface>& fs) 71 { 72 #if BARE_METAL 73 return static_cast<uint8_t>(BmcMode::BM_MODE); 74 #else 75 std::error_code ec; 76 77 if (fs->exists(bmDriveCleaningDoneAckFlagPath, ec)) 78 { 79 std::fprintf( 80 stderr, 81 "%s exists so we acked cleaning done and must be in BM mode\n", 82 bmDriveCleaningDoneAckFlagPath); 83 return static_cast<uint8_t>(BmcMode::BM_MODE); 84 } 85 86 if (fs->exists(bmDriveCleaningDoneFlagPath, ec)) 87 { 88 fs->rename(bmDriveCleaningDoneFlagPath, bmDriveCleaningDoneAckFlagPath, 89 ec); 90 std::fprintf( 91 stderr, 92 "%s exists so we just finished cleaning and must be in BM mode\n", 93 bmDriveCleaningDoneFlagPath); 94 return static_cast<uint8_t>(BmcMode::BM_MODE); 95 } 96 97 if (fs->exists(BM_SIGNAL_PATH, ec)) 98 { 99 if (!fs->exists(bmDriveCleaningFlagPath, ec)) 100 { 101 fs->create(bmDriveCleaningFlagPath); 102 } 103 104 std::fprintf( 105 stderr, 106 "%s exists and no done/ack flag, we must be in BM cleaning mode\n", 107 BM_SIGNAL_PATH); 108 return static_cast<uint8_t>(BmcMode::BM_CLEANING_MODE); 109 } 110 111 std::fprintf( 112 stderr, 113 "Unable to find any BM state files so we must not be in BM mode\n"); 114 return static_cast<uint8_t>(BmcMode::NON_BM_MODE); 115 #endif 116 } 117 118 uint8_t Handler::getBmcMode() 119 { 120 // BM_CLEANING_MODE is not implemented yet 121 return isBmcInBareMetalMode(this->getFs()); 122 } 123 124 std::tuple<std::uint8_t, std::string> 125 Handler::getEthDetails(std::string intf) const 126 { 127 if (intf.empty()) 128 { 129 intf = NCSI_IF_NAME_STR; 130 } 131 return std::make_tuple(::ipmi::getChannelByName(intf), std::move(intf)); 132 } 133 134 std::int64_t Handler::getRxPackets(const std::string& name) const 135 { 136 std::ostringstream opath; 137 opath << "/sys/class/net/" << name << "/statistics/rx_packets"; 138 std::string path = opath.str(); 139 140 // Minor sanity & security check (of course, I'm less certain if unicode 141 // comes into play here. 142 // 143 // Basically you can't easily inject ../ or /../ into the path below. 144 if (name.find("/") != std::string::npos) 145 { 146 std::fprintf(stderr, "Invalid or illegal name: '%s'\n", name.c_str()); 147 throw IpmiException(::ipmi::ccInvalidFieldRequest); 148 } 149 150 std::error_code ec; 151 if (!this->getFs()->exists(path, ec)) 152 { 153 std::fprintf(stderr, "Path: '%s' doesn't exist.\n", path.c_str()); 154 throw IpmiException(::ipmi::ccInvalidFieldRequest); 155 } 156 // We're uninterested in the state of ec. 157 158 int64_t count = 0; 159 std::ifstream ifs; 160 ifs.exceptions(std::ifstream::failbit); 161 try 162 { 163 ifs.open(path); 164 ifs >> count; 165 } 166 catch (std::ios_base::failure& fail) 167 { 168 throw IpmiException(::ipmi::ccUnspecifiedError); 169 } 170 171 return count; 172 } 173 174 VersionTuple Handler::getCpldVersion(unsigned int id) const 175 { 176 std::ostringstream opath; 177 opath << "/run/cpld" << id << ".version"; 178 // Check for file 179 180 std::error_code ec; 181 if (!this->getFs()->exists(opath.str(), ec)) 182 { 183 std::fprintf(stderr, "Path: '%s' doesn't exist.\n", 184 opath.str().c_str()); 185 throw IpmiException(::ipmi::ccInvalidFieldRequest); 186 } 187 // We're uninterested in the state of ec. 188 189 // If file exists, read. 190 std::ifstream ifs; 191 ifs.exceptions(std::ifstream::failbit); 192 std::string value; 193 try 194 { 195 ifs.open(opath.str()); 196 ifs >> value; 197 } 198 catch (std::ios_base::failure& fail) 199 { 200 throw IpmiException(::ipmi::ccUnspecifiedError); 201 } 202 203 // If value parses as expected, return version. 204 VersionTuple version = std::make_tuple(0, 0, 0, 0); 205 206 int num_fields = std::sscanf(value.c_str(), 207 "%" SCNu8 ".%" SCNu8 ".%" SCNu8 ".%" SCNu8, 208 &std::get<0>(version), &std::get<1>(version), 209 &std::get<2>(version), &std::get<3>(version)); 210 if (num_fields == 0) 211 { 212 std::fprintf(stderr, "Invalid version.\n"); 213 throw IpmiException(::ipmi::ccUnspecifiedError); 214 } 215 216 return version; 217 } 218 219 static constexpr auto TIME_DELAY_FILENAME = "/run/psu_timedelay"; 220 static constexpr auto SYSTEMD_SERVICE = "org.freedesktop.systemd1"; 221 static constexpr auto SYSTEMD_ROOT = "/org/freedesktop/systemd1"; 222 static constexpr auto SYSTEMD_INTERFACE = "org.freedesktop.systemd1.Manager"; 223 static constexpr auto PSU_HARDRESET_TARGET = "gbmc-psu-hardreset.target"; 224 225 void Handler::psuResetDelay(std::uint32_t delay) const 226 { 227 std::ofstream ofs; 228 ofs.open(TIME_DELAY_FILENAME, std::ofstream::out); 229 if (!ofs.good()) 230 { 231 std::fprintf(stderr, "Unable to open file for output.\n"); 232 throw IpmiException(::ipmi::ccUnspecifiedError); 233 } 234 235 ofs << "PSU_HARDRESET_DELAY=" << delay << std::endl; 236 if (ofs.fail()) 237 { 238 std::fprintf(stderr, "Write failed\n"); 239 ofs.close(); 240 throw IpmiException(::ipmi::ccUnspecifiedError); 241 } 242 243 // Write succeeded, please continue. 244 ofs.flush(); 245 ofs.close(); 246 247 auto bus = sdbusplus::bus::new_default(); 248 auto method = bus.new_method_call(SYSTEMD_SERVICE, SYSTEMD_ROOT, 249 SYSTEMD_INTERFACE, "StartUnit"); 250 251 method.append(PSU_HARDRESET_TARGET); 252 method.append("replace"); 253 254 try 255 { 256 bus.call_noreply(method); 257 } 258 catch (const sdbusplus::exception::SdBusError& ex) 259 { 260 log<level::ERR>("Failed to call PSU hard reset"); 261 throw IpmiException(::ipmi::ccUnspecifiedError); 262 } 263 } 264 265 static constexpr auto RESET_ON_SHUTDOWN_FILENAME = "/run/powercycle_on_s5"; 266 267 void Handler::psuResetOnShutdown() const 268 { 269 std::ofstream ofs; 270 ofs.open(RESET_ON_SHUTDOWN_FILENAME, std::ofstream::out); 271 if (!ofs.good()) 272 { 273 std::fprintf(stderr, "Unable to open file for output.\n"); 274 throw IpmiException(::ipmi::ccUnspecifiedError); 275 } 276 ofs.close(); 277 } 278 279 uint32_t Handler::getFlashSize() 280 { 281 mtd_info_t info; 282 int fd = open("/dev/mtd0", O_RDONLY); 283 int err = ioctl(fd, MEMGETINFO, &info); 284 close(fd); 285 286 if (err) 287 { 288 throw IpmiException(::ipmi::ccUnspecifiedError); 289 } 290 return info.size; 291 } 292 293 std::string Handler::getEntityName(std::uint8_t id, std::uint8_t instance) 294 { 295 // Check if we support this Entity ID. 296 auto it = _entityIdToName.find(id); 297 if (it == _entityIdToName.end()) 298 { 299 log<level::ERR>("Unknown Entity ID", entry("ENTITY_ID=%d", id)); 300 throw IpmiException(::ipmi::ccInvalidFieldRequest); 301 } 302 303 std::string entityName; 304 try 305 { 306 // Parse the JSON config file. 307 if (!_entityConfigParsed) 308 { 309 _entityConfig = parseConfig(_configFile); 310 _entityConfigParsed = true; 311 } 312 313 // Find the "entity id:entity instance" mapping to entity name. 314 entityName = readNameFromConfig(it->second, instance, _entityConfig); 315 if (entityName.empty()) 316 { 317 throw IpmiException(::ipmi::ccInvalidFieldRequest); 318 } 319 } 320 catch (InternalFailure& e) 321 { 322 throw IpmiException(::ipmi::ccUnspecifiedError); 323 } 324 325 return entityName; 326 } 327 328 std::string Handler::getMachineName() 329 { 330 const char* path = "/etc/os-release"; 331 std::ifstream ifs(path); 332 if (ifs.fail()) 333 { 334 std::fprintf(stderr, "Failed to open: %s\n", path); 335 throw IpmiException(::ipmi::ccUnspecifiedError); 336 } 337 338 std::string line; 339 while (true) 340 { 341 std::getline(ifs, line); 342 if (ifs.eof()) 343 { 344 std::fprintf(stderr, "Failed to find OPENBMC_TARGET_MACHINE: %s\n", 345 path); 346 throw IpmiException(::ipmi::ccInvalidCommand); 347 } 348 if (ifs.fail()) 349 { 350 std::fprintf(stderr, "Failed to read: %s\n", path); 351 throw IpmiException(::ipmi::ccUnspecifiedError); 352 } 353 std::string_view lineView(line); 354 constexpr std::string_view prefix = "OPENBMC_TARGET_MACHINE="; 355 if (lineView.substr(0, prefix.size()) != prefix) 356 { 357 continue; 358 } 359 lineView.remove_prefix(prefix.size()); 360 lineView.remove_prefix( 361 std::min(lineView.find_first_not_of('"'), lineView.size())); 362 lineView.remove_suffix( 363 lineView.size() - 1 - 364 std::min(lineView.find_last_not_of('"'), lineView.size() - 1)); 365 return std::string(lineView); 366 } 367 } 368 369 static constexpr auto HOST_TIME_DELAY_FILENAME = "/run/host_poweroff_delay"; 370 static constexpr auto HOST_POWEROFF_TARGET = "gbmc-host-poweroff.target"; 371 372 void Handler::hostPowerOffDelay(std::uint32_t delay) const 373 { 374 // Set time delay 375 std::ofstream ofs; 376 ofs.open(HOST_TIME_DELAY_FILENAME, std::ofstream::out); 377 if (!ofs.good()) 378 { 379 std::fprintf(stderr, "Unable to open file for output.\n"); 380 throw IpmiException(::ipmi::ccUnspecifiedError); 381 } 382 383 ofs << "HOST_POWEROFF_DELAY=" << delay << std::endl; 384 ofs.close(); 385 if (ofs.fail()) 386 { 387 std::fprintf(stderr, "Write failed\n"); 388 throw IpmiException(::ipmi::ccUnspecifiedError); 389 } 390 391 // Write succeeded, please continue. 392 auto bus = sdbusplus::bus::new_default(); 393 auto method = bus.new_method_call(SYSTEMD_SERVICE, SYSTEMD_ROOT, 394 SYSTEMD_INTERFACE, "StartUnit"); 395 396 method.append(HOST_POWEROFF_TARGET); 397 method.append("replace"); 398 399 try 400 { 401 bus.call_noreply(method); 402 } 403 catch (const sdbusplus::exception::SdBusError& ex) 404 { 405 log<level::ERR>("Failed to call Power Off", 406 entry("WHAT=%s", ex.what())); 407 throw IpmiException(::ipmi::ccUnspecifiedError); 408 } 409 } 410 411 std::string readNameFromConfig(const std::string& type, uint8_t instance, 412 const Json& config) 413 { 414 static const std::vector<Json> empty{}; 415 std::vector<Json> readings = config.value(type, empty); 416 std::string name = ""; 417 418 for (const auto& j : readings) 419 { 420 uint8_t instanceNum = j.value("instance", 0); 421 // Not the instance we're interested in 422 if (instanceNum != instance) 423 { 424 continue; 425 } 426 427 // Found the instance we're interested in 428 name = j.value("name", ""); 429 430 break; 431 } 432 433 return name; 434 } 435 436 void Handler::buildI2cPcieMapping() 437 { 438 _pcie_i2c_map = buildPcieMap(); 439 } 440 441 size_t Handler::getI2cPcieMappingSize() const 442 { 443 return _pcie_i2c_map.size(); 444 } 445 446 std::tuple<std::uint32_t, std::string> 447 Handler::getI2cEntry(unsigned int entry) const 448 { 449 return _pcie_i2c_map[entry]; 450 } 451 452 namespace 453 { 454 455 static constexpr std::string_view ACCEL_OOB_ROOT = "/com/google/customAccel/"; 456 static constexpr char ACCEL_OOB_SERVICE[] = "com.google.custom_accel"; 457 static constexpr char ACCEL_OOB_INTERFACE[] = "com.google.custom_accel.BAR"; 458 459 // C type for "a{oa{sa{sv}}}" from DBus.ObjectManager::GetManagedObjects() 460 using AnyType = std::variant<std::string, uint8_t, uint32_t, uint64_t>; 461 using AnyTypeList = std::vector<std::pair<std::string, AnyType>>; 462 using NamedArrayOfAnyTypeLists = 463 std::vector<std::pair<std::string, AnyTypeList>>; 464 using ArrayOfObjectPathsAndTieredAnyTypeLists = std::vector< 465 std::pair<sdbusplus::message::object_path, NamedArrayOfAnyTypeLists>>; 466 467 } // namespace 468 469 sdbusplus::bus_t Handler::getDbus() const 470 { 471 return sdbusplus::bus::new_default(); 472 } 473 474 const std::unique_ptr<FileSystemInterface>& Handler::getFs() const 475 { 476 return this->fsPtr; 477 } 478 479 uint32_t Handler::accelOobDeviceCount() const 480 { 481 ArrayOfObjectPathsAndTieredAnyTypeLists data; 482 483 try 484 { 485 auto bus = getDbus(); 486 auto method = bus.new_method_call(ACCEL_OOB_SERVICE, "/", 487 "org.freedesktop.DBus.ObjectManager", 488 "GetManagedObjects"); 489 bus.call(method).read(data); 490 } 491 catch (const sdbusplus::exception::SdBusError& ex) 492 { 493 log<level::ERR>( 494 "Failed to call GetManagedObjects on com.google.custom_accel", 495 entry("WHAT=%s", ex.what())); 496 throw IpmiException(::ipmi::ccUnspecifiedError); 497 } 498 499 return data.size(); 500 } 501 502 std::string Handler::accelOobDeviceName(size_t index) const 503 { 504 ArrayOfObjectPathsAndTieredAnyTypeLists data; 505 506 try 507 { 508 auto bus = getDbus(); 509 auto method = bus.new_method_call(ACCEL_OOB_SERVICE, "/", 510 "org.freedesktop.DBus.ObjectManager", 511 "GetManagedObjects"); 512 bus.call(method).read(data); 513 } 514 catch (const sdbusplus::exception::SdBusError& ex) 515 { 516 log<level::ERR>( 517 "Failed to call GetManagedObjects on com.google.custom_accel", 518 entry("WHAT=%s", ex.what())); 519 throw IpmiException(::ipmi::ccUnspecifiedError); 520 } 521 522 if (index >= data.size()) 523 { 524 log<level::WARNING>( 525 "Requested index is larger than the number of entries.", 526 entry("INDEX=%zu", index), entry("NUM_NAMES=%zu", data.size())); 527 throw IpmiException(::ipmi::ccParmOutOfRange); 528 } 529 530 std::string_view name(data[index].first.str); 531 if (!name.starts_with(ACCEL_OOB_ROOT)) 532 { 533 throw IpmiException(::ipmi::ccInvalidCommand); 534 } 535 name.remove_prefix(ACCEL_OOB_ROOT.length()); 536 return std::string(name); 537 } 538 539 uint64_t Handler::accelOobRead(std::string_view name, uint64_t address, 540 uint8_t num_bytes) const 541 { 542 static constexpr char ACCEL_OOB_METHOD[] = "Read"; 543 544 std::string object_name(ACCEL_OOB_ROOT); 545 object_name.append(name); 546 547 auto bus = getDbus(); 548 auto method = bus.new_method_call(ACCEL_OOB_SERVICE, object_name.c_str(), 549 ACCEL_OOB_INTERFACE, ACCEL_OOB_METHOD); 550 method.append(address, static_cast<uint64_t>(num_bytes)); 551 552 std::vector<uint8_t> bytes; 553 554 try 555 { 556 bus.call(method).read(bytes); 557 } 558 catch (const sdbusplus::exception::SdBusError& ex) 559 { 560 log<level::ERR>("Failed to call Read on com.google.custom_accel", 561 entry("WHAT=%s", ex.what()), 562 entry("DBUS_SERVICE=%s", ACCEL_OOB_SERVICE), 563 entry("DBUS_OBJECT=%s", object_name.c_str()), 564 entry("DBUS_INTERFACE=%s", ACCEL_OOB_INTERFACE), 565 entry("DBUS_METHOD=%s", ACCEL_OOB_METHOD), 566 entry("DBUS_ARG_ADDRESS=%016llx", address), 567 entry("DBUS_ARG_NUM_BYTES=%zu", (size_t)num_bytes)); 568 throw IpmiException(::ipmi::ccUnspecifiedError); 569 } 570 571 if (bytes.size() < num_bytes) 572 { 573 log<level::ERR>( 574 "Call to Read on com.google.custom_accel didn't return the expected" 575 " number of bytes.", 576 entry("DBUS_SERVICE=%s", ACCEL_OOB_SERVICE), 577 entry("DBUS_OBJECT=%s", object_name.c_str()), 578 entry("DBUS_INTERFACE=%s", ACCEL_OOB_INTERFACE), 579 entry("DBUS_METHOD=%s", ACCEL_OOB_METHOD), 580 entry("DBUS_ARG_ADDRESS=%016llx", address), 581 entry("DBUS_ARG_NUM_BYTES=%zu", (size_t)num_bytes), 582 entry("DBUS_RETURN_SIZE=%zu", bytes.size())); 583 throw IpmiException(::ipmi::ccUnspecifiedError); 584 } 585 586 if (bytes.size() > sizeof(uint64_t)) 587 { 588 log<level::ERR>( 589 "Call to Read on com.google.custom_accel returned more than 8B.", 590 entry("DBUS_SERVICE=%s", ACCEL_OOB_SERVICE), 591 entry("DBUS_OBJECT=%s", object_name.c_str()), 592 entry("DBUS_INTERFACE=%s", ACCEL_OOB_INTERFACE), 593 entry("DBUS_METHOD=%s", ACCEL_OOB_METHOD), 594 entry("DBUS_ARG_ADDRESS=%016llx", address), 595 entry("DBUS_ARG_NUM_BYTES=%zu", (size_t)num_bytes), 596 entry("DBUS_RETURN_SIZE=%zu", bytes.size())); 597 throw IpmiException(::ipmi::ccReqDataTruncated); 598 } 599 600 uint64_t data = 0; 601 for (size_t i = 0; i < num_bytes; ++i) 602 { 603 data = (data << 8) | bytes[i]; 604 } 605 606 return data; 607 } 608 609 void Handler::accelOobWrite(std::string_view name, uint64_t address, 610 uint8_t num_bytes, uint64_t data) const 611 { 612 static constexpr std::string_view ACCEL_OOB_METHOD = "Write"; 613 614 std::string object_name(ACCEL_OOB_ROOT); 615 object_name.append(name); 616 617 if (num_bytes > sizeof(data)) 618 { 619 log<level::ERR>( 620 "Call to Write on com.google.custom_accel requested more than 8B.", 621 entry("DBUS_SERVICE=%s", ACCEL_OOB_SERVICE), 622 entry("DBUS_OBJECT=%s", object_name.c_str()), 623 entry("DBUS_INTERFACE=%s", ACCEL_OOB_INTERFACE), 624 entry("DBUS_METHOD=%s", ACCEL_OOB_METHOD.data()), 625 entry("DBUS_ARG_ADDRESS=%016llx", address), 626 entry("DBUS_ARG_NUM_BYTES=%zu", (size_t)num_bytes), 627 entry("DBUS_ARG_DATA=%016llx", data)); 628 throw IpmiException(::ipmi::ccParmOutOfRange); 629 } 630 631 std::vector<uint8_t> bytes; 632 bytes.reserve(num_bytes); 633 for (size_t i = 0; i < num_bytes; ++i) 634 { 635 bytes.emplace_back(data & 0xff); 636 data >>= 8; 637 } 638 639 try 640 { 641 auto bus = getDbus(); 642 auto method = 643 bus.new_method_call(ACCEL_OOB_SERVICE, object_name.c_str(), 644 ACCEL_OOB_INTERFACE, ACCEL_OOB_METHOD.data()); 645 method.append(address, bytes); 646 bus.call_noreply(method); 647 } 648 catch (const sdbusplus::exception::SdBusError& ex) 649 { 650 log<level::ERR>("Failed to call Write on com.google.custom_accel", 651 entry("WHAT=%s", ex.what()), 652 entry("DBUS_SERVICE=%s", ACCEL_OOB_SERVICE), 653 entry("DBUS_OBJECT=%s", object_name.c_str()), 654 entry("DBUS_INTERFACE=%s", ACCEL_OOB_INTERFACE), 655 entry("DBUS_METHOD=%s", ACCEL_OOB_METHOD.data()), 656 entry("DBUS_ARG_ADDRESS=%016llx", address), 657 entry("DBUS_ARG_NUM_BYTES=%zu", (size_t)num_bytes), 658 entry("DBUS_ARG_DATA=%016llx", data)); 659 throw IpmiException(::ipmi::ccUnspecifiedError); 660 } 661 } 662 663 std::vector<uint8_t> Handler::pcieBifurcation(uint8_t index) 664 { 665 return bifurcationHelper.get().getBifurcation(index).value_or( 666 std::vector<uint8_t>{}); 667 } 668 669 static constexpr auto BARE_METAL_TARGET = "gbmc-bare-metal-active.target"; 670 671 void Handler::linuxBootDone() const 672 { 673 if (isBmcInBareMetalMode(this->fsPtr) != 674 static_cast<uint8_t>(BmcMode::BM_MODE)) 675 { 676 return; 677 } 678 679 log<level::INFO>("LinuxBootDone: Disabling IPMI"); 680 681 // Start the bare metal active systemd target. 682 auto bus = sdbusplus::bus::new_default(); 683 auto method = bus.new_method_call(SYSTEMD_SERVICE, SYSTEMD_ROOT, 684 SYSTEMD_INTERFACE, "StartUnit"); 685 686 method.append(BARE_METAL_TARGET); 687 method.append("replace"); 688 689 try 690 { 691 bus.call_noreply(method); 692 } 693 catch (const sdbusplus::exception::SdBusError& ex) 694 { 695 log<level::ERR>("Failed to start bare metal active systemd target", 696 entry("WHAT=%s", ex.what())); 697 throw IpmiException(::ipmi::ccUnspecifiedError); 698 } 699 } 700 701 } // namespace ipmi 702 } // namespace google 703