xref: /openbmc/google-ipmi-sys/handler.cpp (revision 8d3d46a2)
1 /*
2  * Copyright 2019 Google Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include "handler.hpp"
18 
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 <cinttypes>
31 #include <cstdio>
32 #include <filesystem>
33 #include <fstream>
34 #include <map>
35 #include <nlohmann/json.hpp>
36 #include <phosphor-logging/elog-errors.hpp>
37 #include <phosphor-logging/log.hpp>
38 #include <sdbusplus/bus.hpp>
39 #include <sstream>
40 #include <string>
41 #include <string_view>
42 #include <tuple>
43 #include <xyz/openbmc_project/Common/error.hpp>
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 std::tuple<std::uint8_t, std::string> Handler::getEthDetails() const
70 {
71     return std::make_tuple<std::uint8_t, std::string>(
72         ::ipmi::getChannelByName(NCSI_IF_NAME_STR), NCSI_IF_NAME_STR);
73 }
74 
75 std::int64_t Handler::getRxPackets(const std::string& name) const
76 {
77     std::ostringstream opath;
78     opath << "/sys/class/net/" << name << "/statistics/rx_packets";
79     std::string path = opath.str();
80 
81     // Minor sanity & security check (of course, I'm less certain if unicode
82     // comes into play here.
83     //
84     // Basically you can't easily inject ../ or /../ into the path below.
85     if (name.find("/") != std::string::npos)
86     {
87         std::fprintf(stderr, "Invalid or illegal name: '%s'\n", name.c_str());
88         throw IpmiException(IPMI_CC_INVALID_FIELD_REQUEST);
89     }
90 
91     std::error_code ec;
92     if (!fs::exists(path, ec))
93     {
94         std::fprintf(stderr, "Path: '%s' doesn't exist.\n", path.c_str());
95         throw IpmiException(IPMI_CC_INVALID_FIELD_REQUEST);
96     }
97     // We're uninterested in the state of ec.
98 
99     int64_t count = 0;
100     std::ifstream ifs;
101     ifs.exceptions(std::ifstream::failbit);
102     try
103     {
104         ifs.open(path);
105         ifs >> count;
106     }
107     catch (std::ios_base::failure& fail)
108     {
109         throw IpmiException(IPMI_CC_UNSPECIFIED_ERROR);
110     }
111 
112     return count;
113 }
114 
115 VersionTuple Handler::getCpldVersion(unsigned int id) const
116 {
117     std::ostringstream opath;
118     opath << "/run/cpld" << id << ".version";
119     // Check for file
120 
121     std::error_code ec;
122     if (!fs::exists(opath.str(), ec))
123     {
124         std::fprintf(stderr, "Path: '%s' doesn't exist.\n",
125                      opath.str().c_str());
126         throw IpmiException(IPMI_CC_INVALID_FIELD_REQUEST);
127     }
128     // We're uninterested in the state of ec.
129 
130     // If file exists, read.
131     std::ifstream ifs;
132     ifs.exceptions(std::ifstream::failbit);
133     std::string value;
134     try
135     {
136         ifs.open(opath.str());
137         ifs >> value;
138     }
139     catch (std::ios_base::failure& fail)
140     {
141         throw IpmiException(IPMI_CC_UNSPECIFIED_ERROR);
142     }
143 
144     // If value parses as expected, return version.
145     VersionTuple version = std::make_tuple(0, 0, 0, 0);
146 
147     int num_fields =
148         std::sscanf(value.c_str(), "%" SCNu8 ".%" SCNu8 ".%" SCNu8 ".%" SCNu8,
149                     &std::get<0>(version), &std::get<1>(version),
150                     &std::get<2>(version), &std::get<3>(version));
151     if (num_fields == 0)
152     {
153         std::fprintf(stderr, "Invalid version.\n");
154         throw IpmiException(IPMI_CC_UNSPECIFIED_ERROR);
155     }
156 
157     return version;
158 }
159 
160 static constexpr auto TIME_DELAY_FILENAME = "/run/psu_timedelay";
161 static constexpr auto SYSTEMD_SERVICE = "org.freedesktop.systemd1";
162 static constexpr auto SYSTEMD_ROOT = "/org/freedesktop/systemd1";
163 static constexpr auto SYSTEMD_INTERFACE = "org.freedesktop.systemd1.Manager";
164 static constexpr auto PSU_HARDRESET_TARGET = "gbmc-psu-hardreset.target";
165 
166 void Handler::psuResetDelay(std::uint32_t delay) const
167 {
168     std::ofstream ofs;
169     ofs.open(TIME_DELAY_FILENAME, std::ofstream::out);
170     if (!ofs.good())
171     {
172         std::fprintf(stderr, "Unable to open file for output.\n");
173         throw IpmiException(IPMI_CC_UNSPECIFIED_ERROR);
174     }
175 
176     ofs << "PSU_HARDRESET_DELAY=" << delay << std::endl;
177     if (ofs.fail())
178     {
179         std::fprintf(stderr, "Write failed\n");
180         ofs.close();
181         throw IpmiException(IPMI_CC_UNSPECIFIED_ERROR);
182     }
183 
184     // Write succeeded, please continue.
185     ofs.flush();
186     ofs.close();
187 
188     auto bus = sdbusplus::bus::new_default();
189     auto method = bus.new_method_call(SYSTEMD_SERVICE, SYSTEMD_ROOT,
190                                       SYSTEMD_INTERFACE, "StartUnit");
191 
192     method.append(PSU_HARDRESET_TARGET);
193     method.append("replace");
194 
195     try
196     {
197         bus.call_noreply(method);
198     }
199     catch (const sdbusplus::exception::SdBusError& ex)
200     {
201         log<level::ERR>("Failed to call PSU hard reset");
202         throw IpmiException(IPMI_CC_UNSPECIFIED_ERROR);
203     }
204 }
205 
206 static constexpr auto RESET_ON_SHUTDOWN_FILENAME = "/run/powercycle_on_s5";
207 
208 void Handler::psuResetOnShutdown() const
209 {
210     std::ofstream ofs;
211     ofs.open(RESET_ON_SHUTDOWN_FILENAME, std::ofstream::out);
212     if (!ofs.good())
213     {
214         std::fprintf(stderr, "Unable to open file for output.\n");
215         throw IpmiException(IPMI_CC_UNSPECIFIED_ERROR);
216     }
217     ofs.close();
218 }
219 
220 uint32_t Handler::getFlashSize()
221 {
222     mtd_info_t info;
223     int fd = open("/dev/mtd0", O_RDONLY);
224     int err = ioctl(fd, MEMGETINFO, &info);
225     close(fd);
226 
227     if (err)
228     {
229         throw IpmiException(IPMI_CC_UNSPECIFIED_ERROR);
230     }
231     return info.size;
232 }
233 
234 std::string Handler::getEntityName(std::uint8_t id, std::uint8_t instance)
235 {
236     // Check if we support this Entity ID.
237     auto it = _entityIdToName.find(id);
238     if (it == _entityIdToName.end())
239     {
240         log<level::ERR>("Unknown Entity ID", entry("ENTITY_ID=%d", id));
241         throw IpmiException(IPMI_CC_INVALID_FIELD_REQUEST);
242     }
243 
244     std::string entityName;
245     try
246     {
247         // Parse the JSON config file.
248         if (!_entityConfigParsed)
249         {
250             _entityConfig = parseConfig(_configFile);
251             _entityConfigParsed = true;
252         }
253 
254         // Find the "entity id:entity instance" mapping to entity name.
255         entityName = readNameFromConfig(it->second, instance, _entityConfig);
256         if (entityName.empty())
257         {
258             throw IpmiException(IPMI_CC_INVALID_FIELD_REQUEST);
259         }
260     }
261     catch (InternalFailure& e)
262     {
263         throw IpmiException(IPMI_CC_UNSPECIFIED_ERROR);
264     }
265 
266     return entityName;
267 }
268 
269 std::string Handler::getMachineName()
270 {
271     const char* path = "/etc/os-release";
272     std::ifstream ifs(path);
273     if (ifs.fail())
274     {
275         std::fprintf(stderr, "Failed to open: %s\n", path);
276         throw IpmiException(IPMI_CC_UNSPECIFIED_ERROR);
277     }
278 
279     std::string line;
280     while (true)
281     {
282         std::getline(ifs, line);
283         if (ifs.eof())
284         {
285             std::fprintf(stderr, "Failed to find OPENBMC_TARGET_MACHINE: %s\n",
286                          path);
287             throw IpmiException(IPMI_CC_INVALID);
288         }
289         if (ifs.fail())
290         {
291             std::fprintf(stderr, "Failed to read: %s\n", path);
292             throw IpmiException(IPMI_CC_UNSPECIFIED_ERROR);
293         }
294         std::string_view lineView(line);
295         constexpr std::string_view prefix = "OPENBMC_TARGET_MACHINE=";
296         if (lineView.substr(0, prefix.size()) != prefix)
297         {
298             continue;
299         }
300         lineView.remove_prefix(prefix.size());
301         lineView.remove_prefix(
302             std::min(lineView.find_first_not_of('"'), lineView.size()));
303         lineView.remove_suffix(
304             lineView.size() - 1 -
305             std::min(lineView.find_last_not_of('"'), lineView.size() - 1));
306         return std::string(lineView);
307     }
308 }
309 
310 static constexpr auto HOST_TIME_DELAY_FILENAME = "/run/host_poweroff_delay";
311 static constexpr auto HOST_POWEROFF_TARGET = "gbmc-host-poweroff.target";
312 
313 void Handler::hostPowerOffDelay(std::uint32_t delay) const
314 {
315     // Set time delay
316     std::ofstream ofs;
317     ofs.open(HOST_TIME_DELAY_FILENAME, std::ofstream::out);
318     if (!ofs.good())
319     {
320         std::fprintf(stderr, "Unable to open file for output.\n");
321         throw IpmiException(IPMI_CC_UNSPECIFIED_ERROR);
322     }
323 
324     ofs << "HOST_POWEROFF_DELAY=" << delay << std::endl;
325     ofs.close();
326     if (ofs.fail())
327     {
328         std::fprintf(stderr, "Write failed\n");
329         throw IpmiException(IPMI_CC_UNSPECIFIED_ERROR);
330     }
331 
332     // Write succeeded, please continue.
333     auto bus = sdbusplus::bus::new_default();
334     auto method = bus.new_method_call(SYSTEMD_SERVICE, SYSTEMD_ROOT,
335                                       SYSTEMD_INTERFACE, "StartUnit");
336 
337     method.append(HOST_POWEROFF_TARGET);
338     method.append("replace");
339 
340     try
341     {
342         bus.call_noreply(method);
343     }
344     catch (const sdbusplus::exception::SdBusError& ex)
345     {
346         log<level::ERR>("Failed to call Power Off",
347                         entry("WHAT=%s", ex.what()));
348         throw IpmiException(IPMI_CC_UNSPECIFIED_ERROR);
349     }
350 }
351 
352 std::string readNameFromConfig(const std::string& type, uint8_t instance,
353                                const Json& config)
354 {
355     static const std::vector<Json> empty{};
356     std::vector<Json> readings = config.value(type, empty);
357     std::string name = "";
358 
359     for (const auto& j : readings)
360     {
361         uint8_t instanceNum = j.value("instance", 0);
362         // Not the instance we're interested in
363         if (instanceNum != instance)
364         {
365             continue;
366         }
367 
368         // Found the instance we're interested in
369         name = j.value("name", "");
370 
371         break;
372     }
373 
374     return name;
375 }
376 
377 void Handler::buildI2cPcieMapping()
378 {
379     _pcie_i2c_map = buildPcieMap();
380 }
381 
382 size_t Handler::getI2cPcieMappingSize() const
383 {
384     return _pcie_i2c_map.size();
385 }
386 
387 std::tuple<std::uint32_t, std::string>
388     Handler::getI2cEntry(unsigned int entry) const
389 {
390     return _pcie_i2c_map[entry];
391 }
392 
393 } // namespace ipmi
394 } // namespace google
395