xref: /openbmc/openpower-host-ipmi-oem/oemhandler.cpp (revision 7f25db70041b3275bb80289b29c90baa3fedbd0b)
1 #include "config.h"
2 
3 #include "oemhandler.hpp"
4 
5 #include "elog-errors.hpp"
6 
7 #include <endian.h>
8 #include <ipmid/api.h>
9 #include <stdio.h>
10 #include <string.h>
11 #include <systemd/sd-bus.h>
12 
13 #include <host-interface.hpp>
14 #include <ipmid-host/cmd.hpp>
15 #include <org/open_power/Host/error.hpp>
16 #include <org/open_power/OCC/Metrics/error.hpp>
17 #include <sdbusplus/bus.hpp>
18 #include <sdbusplus/exception.hpp>
19 
20 #include <fstream>
21 #include <functional>
22 #include <memory>
23 
24 void register_netfn_ibm_oem_commands() __attribute__((constructor));
25 
26 const char* g_esel_path = "/tmp/esel";
27 uint16_t g_record_id = 0x0001;
28 using namespace phosphor::logging;
29 constexpr auto occMetricsType = 0xDD;
30 
31 extern const ObjectIDMap invSensors;
32 const std::map<uint8_t, Entry::Level> severityMap{
33     {0x10, Entry::Level::Warning}, // Recoverable error
34     {0x20, Entry::Level::Warning}, // Predictive error
35     {0x40, Entry::Level::Error},   // Unrecoverable error
36     {0x50, Entry::Level::Error},   // Critical error
37     {0x60, Entry::Level::Error},   // Error from a diagnostic test
38     {0x70, Entry::Level::Warning}, // Recoverable symptom
39     {0xFF, Entry::Level::Error},   // Unknown error
40 };
41 
mapSeverity(const std::string & eSELData)42 Entry::Level mapSeverity(const std::string& eSELData)
43 {
44     constexpr size_t severityOffset = 0x4A;
45 
46     if (eSELData.size() > severityOffset)
47     {
48         // Dive in to the IBM log to find the severity
49         uint8_t sev = 0xF0 & eSELData[severityOffset];
50 
51         auto find = severityMap.find(sev);
52         if (find != severityMap.end())
53         {
54             return find->second;
55         }
56     }
57 
58     // Default to Entry::Level::Error if a matching is not found.
59     return Entry::Level::Error;
60 }
61 
mapCalloutAssociation(const std::string & eSELData)62 std::string mapCalloutAssociation(const std::string& eSELData)
63 {
64     auto rec = reinterpret_cast<const SELEventRecord*>(&eSELData[0]);
65     uint8_t sensor = rec->sensorNum;
66 
67     /*
68      * Search the sensor number to inventory path mapping to figure out the
69      * inventory associated with the ESEL.
70      */
71     auto found = std::find_if(invSensors.begin(), invSensors.end(),
72                               [&sensor](const auto& iter) {
73                                   return (iter.second.sensorID == sensor);
74                               });
75     if (found != invSensors.end())
76     {
77         return found->first;
78     }
79 
80     return {};
81 }
82 
getService(sdbusplus::bus_t & bus,const std::string & path,const std::string & interface)83 std::string getService(sdbusplus::bus_t& bus, const std::string& path,
84                        const std::string& interface)
85 {
86     auto method =
87         bus.new_method_call("xyz.openbmc_project.ObjectMapper",
88                             "/xyz/openbmc_project/object_mapper",
89                             "xyz.openbmc_project.ObjectMapper", "GetObject");
90 
91     method.append(path);
92     method.append(std::vector<std::string>({interface}));
93 
94     std::map<std::string, std::vector<std::string>> response;
95 
96     try
97     {
98         auto reply = bus.call(method);
99 
100         reply.read(response);
101         if (response.empty())
102         {
103             log<level::ERR>("Error in mapper response for getting service name",
104                             entry("PATH=%s", path.c_str()),
105                             entry("INTERFACE=%s", interface.c_str()));
106             return std::string{};
107         }
108     }
109     catch (const sdbusplus::exception_t& e)
110     {
111         log<level::ERR>("Error in mapper method call",
112                         entry("ERROR=%s", e.what()));
113         return std::string{};
114     }
115 
116     return response.begin()->first;
117 }
118 
readESEL(const char * fileName)119 std::string readESEL(const char* fileName)
120 {
121     std::string content{};
122 
123     std::ifstream handle(fileName);
124 
125     if (handle.fail())
126     {
127         log<level::ERR>("Failed to open eSEL", entry("FILENAME=%s", fileName));
128         return content;
129     }
130 
131     handle.seekg(0, std::ios::end);
132     content.resize(handle.tellg());
133     handle.seekg(0, std::ios::beg);
134     handle.read(&content[0], content.size());
135     handle.close();
136 
137     return content;
138 }
139 
createOCCLogEntry(const std::string & eSELData)140 void createOCCLogEntry(const std::string& eSELData)
141 {
142     // Each byte in eSEL is formatted as %02x with a space between bytes and
143     // insert '/0' at the end of the character array.
144     constexpr auto byteSeperator = 3;
145 
146     std::unique_ptr<char[]> data(
147         new char[(eSELData.size() * byteSeperator) + 1]());
148 
149     for (size_t i = 0; i < eSELData.size(); i++)
150     {
151         sprintf(&data[i * byteSeperator], "%02x ", eSELData[i]);
152     }
153     data[eSELData.size() * byteSeperator] = '\0';
154 
155     using error = sdbusplus::org::open_power::OCC::Metrics::Error::Event;
156     using metadata = org::open_power::OCC::Metrics::Event;
157 
158     report<error>(metadata::ESEL(data.get()));
159 }
160 
createHostEntry(const std::string & eSELData)161 void createHostEntry(const std::string& eSELData)
162 {
163     // Each byte in eSEL is formatted as %02x with a space between bytes and
164     // insert '/0' at the end of the character array.
165     constexpr auto byteSeperator = 3;
166 
167     auto sev = mapSeverity(eSELData);
168     auto inventoryPath = mapCalloutAssociation(eSELData);
169 
170     if (!inventoryPath.empty())
171     {
172         std::unique_ptr<char[]> data(
173             new char[(eSELData.size() * byteSeperator) + 1]());
174 
175         for (size_t i = 0; i < eSELData.size(); i++)
176         {
177             sprintf(&data[i * byteSeperator], "%02x ", eSELData[i]);
178         }
179         data[eSELData.size() * byteSeperator] = '\0';
180 
181         using hosterror = sdbusplus::org::open_power::Host::Error::Event;
182         using hostmetadata = org::open_power::Host::Event;
183 
184         report<hosterror>(
185             sev, hostmetadata::ESEL(data.get()),
186             hostmetadata::CALLOUT_INVENTORY_PATH(inventoryPath.c_str()));
187     }
188 }
189 
190 /** @brief Helper function to do a graceful restart (reboot) of the BMC.
191     @return 0 on success, -1 on error
192  */
rebootBMC()193 int rebootBMC()
194 {
195     sdbusplus::bus_t bus{ipmid_get_sd_bus_connection()};
196     auto service = getService(bus, stateBmcPath, stateBmcIntf);
197     if (service.empty())
198     {
199         log<level::ERR>("Error getting the service name to reboot the BMC.");
200         return -1;
201     }
202     std::variant<std::string> reboot =
203         "xyz.openbmc_project.State.BMC.Transition.Reboot";
204     auto method = bus.new_method_call(service.c_str(), stateBmcPath,
205                                       propertiesIntf, "Set");
206     method.append(stateBmcIntf, "RequestedBMCTransition", reboot);
207     try
208     {
209         bus.call_noreply(method);
210     }
211     catch (const sdbusplus::exception_t& e)
212     {
213         log<level::ERR>("Error calling to reboot the BMC.",
214                         entry("ERROR=%s", e.what()));
215         return -1;
216     }
217     return 0;
218 }
219 
220 ///////////////////////////////////////////////////////////////////////////////
221 // For the First partial add eSEL the SEL Record ID and offset
222 // value should be 0x0000. The extended data needs to be in
223 // the form of an IPMI SEL Event Record, with Event sensor type
224 // of 0xDF and Event Message format of 0x04. The returned
225 // Record ID should be used for all partial eSEL adds.
226 //
227 // This function creates a /tmp/esel file to store the
228 // incoming partial esel.  It is the role of some other
229 // function to commit the error log in to long term
230 // storage.  Likely via the ipmi add_sel command.
231 ///////////////////////////////////////////////////////////////////////////////
ipmi_ibm_oem_partial_esel(ipmi_netfn_t netfn,ipmi_cmd_t cmd,ipmi_request_t request,ipmi_response_t response,ipmi_data_len_t data_len,ipmi_context_t context)232 ipmi_ret_t ipmi_ibm_oem_partial_esel(
233     [[maybe_unused]] ipmi_netfn_t netfn, [[maybe_unused]] ipmi_cmd_t cmd,
234     [[maybe_unused]] ipmi_request_t request,
235     [[maybe_unused]] ipmi_response_t response, ipmi_data_len_t data_len,
236     [[maybe_unused]] ipmi_context_t context)
237 {
238     uint8_t* reqptr = (uint8_t*)request;
239     esel_request_t esel_req;
240     FILE* fp;
241     int r = 0;
242     uint8_t rlen;
243     ipmi_ret_t rc = IPMI_CC_OK;
244     const char* pio;
245 
246     esel_req.resid = le16toh((((uint16_t)reqptr[1]) << 8) + reqptr[0]);
247     esel_req.selrecord = le16toh((((uint16_t)reqptr[3]) << 8) + reqptr[2]);
248     esel_req.offset = le16toh((((uint16_t)reqptr[5]) << 8) + reqptr[4]);
249     esel_req.progress = reqptr[6];
250 
251     // According to IPMI spec, Reservation ID must be checked.
252     if (!checkSELReservation(esel_req.resid))
253     {
254         // 0xc5 means Reservation Cancelled or Invalid Reservation ID.
255         printf("Used Reservation ID = %d\n", esel_req.resid);
256         rc = IPMI_CC_INVALID_RESERVATION_ID;
257 
258         // clean g_esel_path.
259         r = remove(g_esel_path);
260         if (r < 0)
261             fprintf(stderr, "Error deleting %s\n", g_esel_path);
262 
263         return rc;
264     }
265 
266     // OpenPOWER Host Interface spec says if RecordID and Offset are
267     // 0 then then this is a new request
268     if (!esel_req.selrecord && !esel_req.offset)
269         pio = "wb";
270     else
271         pio = "rb+";
272 
273     rlen = (*data_len) - (uint8_t)(sizeof(esel_request_t));
274 
275     if ((fp = fopen(g_esel_path, pio)) != NULL)
276     {
277         fseek(fp, esel_req.offset, SEEK_SET);
278         fwrite(reqptr + (uint8_t)(sizeof(esel_request_t)), rlen, 1, fp);
279         fclose(fp);
280 
281         *data_len = sizeof(g_record_id);
282         memcpy(response, &g_record_id, *data_len);
283     }
284     else
285     {
286         fprintf(stderr, "Error trying to perform %s for esel%s\n", pio,
287                 g_esel_path);
288         rc = IPMI_CC_INVALID;
289         *data_len = 0;
290     }
291 
292     // The first bit presents that this is the last partial packet
293     // coming down.  If that is the case advance the record id so we
294     // don't overlap logs.  This allows anyone to establish a log
295     // directory system.
296     if (esel_req.progress & 1)
297     {
298         g_record_id++;
299 
300         auto eSELData = readESEL(g_esel_path);
301 
302         if (eSELData.empty())
303         {
304             return IPMI_CC_UNSPECIFIED_ERROR;
305         }
306 
307         // If the eSEL record type is OCC metrics, then create the OCC log
308         // entry.
309         if (eSELData[2] == occMetricsType)
310         {
311             createOCCLogEntry(eSELData);
312         }
313         else
314         {
315             createHostEntry(eSELData);
316         }
317     }
318 
319     return rc;
320 }
321 
322 // Prepare for FW Update.
323 // Execute needed commands to prepare the system for a fw update from the host.
ipmi_ibm_oem_prep_fw_update(ipmi_netfn_t netfn,ipmi_cmd_t cmd,ipmi_request_t request,ipmi_response_t response,ipmi_data_len_t data_len,ipmi_context_t context)324 ipmi_ret_t ipmi_ibm_oem_prep_fw_update(
325     [[maybe_unused]] ipmi_netfn_t netfn, [[maybe_unused]] ipmi_cmd_t cmd,
326     [[maybe_unused]] ipmi_request_t request,
327     [[maybe_unused]] ipmi_response_t response, ipmi_data_len_t data_len,
328     [[maybe_unused]] ipmi_context_t context)
329 {
330     ipmi_ret_t ipmi_rc = IPMI_CC_OK;
331     *data_len = 0;
332 
333     int rc = 0;
334     std::ofstream rwfs_file;
335 
336     // Set one time flag
337     rc = system(
338         "fw_setenv openbmconce copy-files-to-ram copy-base-filesystem-to-ram");
339     rc = WEXITSTATUS(rc);
340     if (rc != 0)
341     {
342         fprintf(stderr, "fw_setenv openbmconce failed with rc=%d\n", rc);
343         return IPMI_CC_UNSPECIFIED_ERROR;
344     }
345 
346     // Touch the image-rwfs file to perform an empty update to force the save
347     // in case we're already in ram and the flash is the same causing the ram
348     // files to not be copied back to flash
349     rwfs_file.open("/run/initramfs/image-rwfs",
350                    std::ofstream::out | std::ofstream::app);
351     rwfs_file.close();
352 
353     // Reboot the BMC for settings to take effect
354     rc = rebootBMC();
355     if (rc < 0)
356     {
357         fprintf(stderr, "Failed to reset BMC: %s\n", strerror(-rc));
358         return -1;
359     }
360     printf("Warning: BMC is going down for reboot!\n");
361 
362     return ipmi_rc;
363 }
364 
ipmi_ibm_oem_bmc_factory_reset(ipmi_netfn_t netfn,ipmi_cmd_t cmd,ipmi_request_t request,ipmi_response_t response,ipmi_data_len_t data_len,ipmi_context_t context)365 ipmi_ret_t ipmi_ibm_oem_bmc_factory_reset(
366     [[maybe_unused]] ipmi_netfn_t netfn, [[maybe_unused]] ipmi_cmd_t cmd,
367     [[maybe_unused]] ipmi_request_t request,
368     [[maybe_unused]] ipmi_response_t response,
369     [[maybe_unused]] ipmi_data_len_t data_len,
370     [[maybe_unused]] ipmi_context_t context)
371 {
372     sdbusplus::bus_t bus{ipmid_get_sd_bus_connection()};
373 
374     // Since this is a one way command (i.e. the host is requesting a power
375     // off of itself and a reboot of the BMC) we can exceed the 5 second
376     // IPMI timeout. Testing has shown that the power off can take up to
377     // 10 seconds so give it at least 15
378     constexpr auto powerOffWait = std::chrono::seconds(15);
379     constexpr auto setFactoryWait = std::chrono::seconds(3);
380 
381     // Power Off Chassis
382     auto service = getService(bus, stateChassisPath, stateChassisIntf);
383     if (service.empty())
384     {
385         return IPMI_CC_UNSPECIFIED_ERROR;
386     }
387     std::variant<std::string> off =
388         "xyz.openbmc_project.State.Chassis.Transition.Off";
389     auto method = bus.new_method_call(service.c_str(), stateChassisPath,
390                                       propertiesIntf, "Set");
391     method.append(stateChassisIntf, "RequestedPowerTransition", off);
392     try
393     {
394         bus.call_noreply(method);
395     }
396     catch (const sdbusplus::exception_t& e)
397     {
398         log<level::ERR>("Error powering off the chassis",
399                         entry("ERROR=%s", e.what()));
400         return IPMI_CC_UNSPECIFIED_ERROR;
401     }
402 
403     // Wait a few seconds for the chassis to power off
404     std::this_thread::sleep_for(powerOffWait);
405 
406     // Set Factory Reset
407     method = bus.new_method_call(bmcUpdaterServiceName, softwarePath,
408                                  factoryResetIntf, "Reset");
409     try
410     {
411         bus.call_noreply(method);
412     }
413     catch (const sdbusplus::exception_t& e)
414     {
415         log<level::ERR>("Error setting factory reset",
416                         entry("ERROR=%s", e.what()));
417         return IPMI_CC_UNSPECIFIED_ERROR;
418     }
419 
420     // Wait a few seconds for service that sets the reset env variable to
421     // complete before the BMC is rebooted
422     std::this_thread::sleep_for(setFactoryWait);
423 
424     // Reboot BMC
425     auto rc = rebootBMC();
426     if (rc < 0)
427     {
428         log<level::ALERT>("The BMC needs to be manually rebooted to complete "
429                           "the factory reset.");
430         return IPMI_CC_UNSPECIFIED_ERROR;
431     }
432 
433     return IPMI_CC_OK;
434 }
435 
436 namespace
437 {
438 // Storage to keep the object alive during process life
439 std::unique_ptr<open_power::host::command::Host> opHost
440     __attribute__((init_priority(101)));
441 std::unique_ptr<sdbusplus::server::manager_t> objManager
442     __attribute__((init_priority(101)));
443 } // namespace
444 
register_netfn_ibm_oem_commands()445 void register_netfn_ibm_oem_commands()
446 {
447     printf("Registering NetFn:[0x%X], Cmd:[0x%X]\n", NETFUN_IBM_OEM,
448            IPMI_CMD_PESEL);
449     ipmi_register_callback(NETFUN_IBM_OEM, IPMI_CMD_PESEL, NULL,
450                            ipmi_ibm_oem_partial_esel, SYSTEM_INTERFACE);
451 
452     printf("Registering NetFn:[0x%X], Cmd:[0x%X]\n", NETFUN_OEM,
453            IPMI_CMD_PREP_FW_UPDATE);
454     ipmi_register_callback(NETFUN_OEM, IPMI_CMD_PREP_FW_UPDATE, NULL,
455                            ipmi_ibm_oem_prep_fw_update, SYSTEM_INTERFACE);
456 
457     ipmi_register_callback(NETFUN_IBM_OEM, IPMI_CMD_BMC_FACTORY_RESET, NULL,
458                            ipmi_ibm_oem_bmc_factory_reset, SYSTEM_INTERFACE);
459 
460     // Create new object on the bus
461     auto objPath = std::string{"/org/open_power/control"} + '/' + HOST_NAME +
462                    '0';
463 
464     // Add sdbusplus ObjectManager.
465     auto& sdbusPlusHandler = ipmid_get_sdbus_plus_handler();
466     objManager = std::make_unique<sdbusplus::server::manager_t>(
467         *sdbusPlusHandler, "/org/open_power/control");
468 
469     opHost = std::make_unique<open_power::host::command::Host>(
470         *sdbusPlusHandler, objPath.c_str());
471 
472     // Service for this is provided by phosphor layer systemcmdintf
473     // and this will be as part of that.
474     return;
475 }
476