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