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