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