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