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