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