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