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