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 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 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 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 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 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 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 */ 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 /////////////////////////////////////////////////////////////////////////////// 230 ipmi_ret_t ipmi_ibm_oem_partial_esel(ipmi_netfn_t netfn, ipmi_cmd_t cmd, 231 ipmi_request_t request, 232 ipmi_response_t response, 233 ipmi_data_len_t data_len, 234 ipmi_context_t context) 235 { 236 uint8_t* reqptr = (uint8_t*)request; 237 esel_request_t esel_req; 238 FILE* fp; 239 int r = 0; 240 uint8_t rlen; 241 ipmi_ret_t rc = IPMI_CC_OK; 242 const char* pio; 243 244 esel_req.resid = le16toh((((uint16_t)reqptr[1]) << 8) + reqptr[0]); 245 esel_req.selrecord = le16toh((((uint16_t)reqptr[3]) << 8) + reqptr[2]); 246 esel_req.offset = le16toh((((uint16_t)reqptr[5]) << 8) + reqptr[4]); 247 esel_req.progress = reqptr[6]; 248 249 // According to IPMI spec, Reservation ID must be checked. 250 if (!checkSELReservation(esel_req.resid)) 251 { 252 // 0xc5 means Reservation Cancelled or Invalid Reservation ID. 253 printf("Used Reservation ID = %d\n", esel_req.resid); 254 rc = IPMI_CC_INVALID_RESERVATION_ID; 255 256 // clean g_esel_path. 257 r = remove(g_esel_path); 258 if (r < 0) 259 fprintf(stderr, "Error deleting %s\n", g_esel_path); 260 261 return rc; 262 } 263 264 // OpenPOWER Host Interface spec says if RecordID and Offset are 265 // 0 then then this is a new request 266 if (!esel_req.selrecord && !esel_req.offset) 267 pio = "wb"; 268 else 269 pio = "rb+"; 270 271 rlen = (*data_len) - (uint8_t)(sizeof(esel_request_t)); 272 273 if ((fp = fopen(g_esel_path, pio)) != NULL) 274 { 275 fseek(fp, esel_req.offset, SEEK_SET); 276 fwrite(reqptr + (uint8_t)(sizeof(esel_request_t)), rlen, 1, fp); 277 fclose(fp); 278 279 *data_len = sizeof(g_record_id); 280 memcpy(response, &g_record_id, *data_len); 281 } 282 else 283 { 284 fprintf(stderr, "Error trying to perform %s for esel%s\n", pio, 285 g_esel_path); 286 rc = IPMI_CC_INVALID; 287 *data_len = 0; 288 } 289 290 // The first bit presents that this is the last partial packet 291 // coming down. If that is the case advance the record id so we 292 // don't overlap logs. This allows anyone to establish a log 293 // directory system. 294 if (esel_req.progress & 1) 295 { 296 g_record_id++; 297 298 auto eSELData = readESEL(g_esel_path); 299 300 if (eSELData.empty()) 301 { 302 return IPMI_CC_UNSPECIFIED_ERROR; 303 } 304 305 // If the eSEL record type is OCC metrics, then create the OCC log 306 // entry. 307 if (eSELData[2] == occMetricsType) 308 { 309 createOCCLogEntry(eSELData); 310 } 311 else 312 { 313 createHostEntry(eSELData); 314 } 315 } 316 317 return rc; 318 } 319 320 // Prepare for FW Update. 321 // Execute needed commands to prepare the system for a fw update from the host. 322 ipmi_ret_t ipmi_ibm_oem_prep_fw_update(ipmi_netfn_t netfn, ipmi_cmd_t cmd, 323 ipmi_request_t request, 324 ipmi_response_t response, 325 ipmi_data_len_t data_len, 326 ipmi_context_t context) 327 { 328 ipmi_ret_t ipmi_rc = IPMI_CC_OK; 329 *data_len = 0; 330 331 int rc = 0; 332 std::ofstream rwfs_file; 333 334 // Set one time flag 335 rc = system( 336 "fw_setenv openbmconce copy-files-to-ram copy-base-filesystem-to-ram"); 337 rc = WEXITSTATUS(rc); 338 if (rc != 0) 339 { 340 fprintf(stderr, "fw_setenv openbmconce failed with rc=%d\n", rc); 341 return IPMI_CC_UNSPECIFIED_ERROR; 342 } 343 344 // Touch the image-rwfs file to perform an empty update to force the save 345 // in case we're already in ram and the flash is the same causing the ram 346 // files to not be copied back to flash 347 rwfs_file.open("/run/initramfs/image-rwfs", 348 std::ofstream::out | std::ofstream::app); 349 rwfs_file.close(); 350 351 // Reboot the BMC for settings to take effect 352 rc = rebootBMC(); 353 if (rc < 0) 354 { 355 fprintf(stderr, "Failed to reset BMC: %s\n", strerror(-rc)); 356 return -1; 357 } 358 printf("Warning: BMC is going down for reboot!\n"); 359 360 return ipmi_rc; 361 } 362 363 ipmi_ret_t ipmi_ibm_oem_bmc_factory_reset(ipmi_netfn_t netfn, ipmi_cmd_t cmd, 364 ipmi_request_t request, 365 ipmi_response_t response, 366 ipmi_data_len_t data_len, 367 ipmi_context_t context) 368 { 369 sdbusplus::bus_t bus{ipmid_get_sd_bus_connection()}; 370 371 // Since this is a one way command (i.e. the host is requesting a power 372 // off of itself and a reboot of the BMC) we can exceed the 5 second 373 // IPMI timeout. Testing has shown that the power off can take up to 374 // 10 seconds so give it at least 15 375 constexpr auto powerOffWait = std::chrono::seconds(15); 376 constexpr auto setFactoryWait = std::chrono::seconds(3); 377 378 // Power Off Chassis 379 auto service = getService(bus, stateChassisPath, stateChassisIntf); 380 if (service.empty()) 381 { 382 return IPMI_CC_UNSPECIFIED_ERROR; 383 } 384 std::variant<std::string> off = 385 "xyz.openbmc_project.State.Chassis.Transition.Off"; 386 auto method = bus.new_method_call(service.c_str(), stateChassisPath, 387 propertiesIntf, "Set"); 388 method.append(stateChassisIntf, "RequestedPowerTransition", off); 389 try 390 { 391 bus.call_noreply(method); 392 } 393 catch (const sdbusplus::exception_t& e) 394 { 395 log<level::ERR>("Error powering off the chassis", 396 entry("ERROR=%s", e.what())); 397 return IPMI_CC_UNSPECIFIED_ERROR; 398 } 399 400 // Wait a few seconds for the chassis to power off 401 std::this_thread::sleep_for(powerOffWait); 402 403 // Set Factory Reset 404 method = bus.new_method_call(bmcUpdaterServiceName, softwarePath, 405 factoryResetIntf, "Reset"); 406 try 407 { 408 bus.call_noreply(method); 409 } 410 catch (const sdbusplus::exception_t& e) 411 { 412 log<level::ERR>("Error setting factory reset", 413 entry("ERROR=%s", e.what())); 414 return IPMI_CC_UNSPECIFIED_ERROR; 415 } 416 417 // Wait a few seconds for service that sets the reset env variable to 418 // complete before the BMC is rebooted 419 std::this_thread::sleep_for(setFactoryWait); 420 421 // Reboot BMC 422 auto rc = rebootBMC(); 423 if (rc < 0) 424 { 425 log<level::ALERT>("The BMC needs to be manually rebooted to complete " 426 "the factory reset."); 427 return IPMI_CC_UNSPECIFIED_ERROR; 428 } 429 430 return IPMI_CC_OK; 431 } 432 433 namespace 434 { 435 // Storage to keep the object alive during process life 436 std::unique_ptr<open_power::host::command::Host> opHost 437 __attribute__((init_priority(101))); 438 std::unique_ptr<sdbusplus::server::manager_t> objManager 439 __attribute__((init_priority(101))); 440 } // namespace 441 442 void register_netfn_ibm_oem_commands() 443 { 444 printf("Registering NetFn:[0x%X], Cmd:[0x%X]\n", NETFUN_IBM_OEM, 445 IPMI_CMD_PESEL); 446 ipmi_register_callback(NETFUN_IBM_OEM, IPMI_CMD_PESEL, NULL, 447 ipmi_ibm_oem_partial_esel, SYSTEM_INTERFACE); 448 449 printf("Registering NetFn:[0x%X], Cmd:[0x%X]\n", NETFUN_OEM, 450 IPMI_CMD_PREP_FW_UPDATE); 451 ipmi_register_callback(NETFUN_OEM, IPMI_CMD_PREP_FW_UPDATE, NULL, 452 ipmi_ibm_oem_prep_fw_update, SYSTEM_INTERFACE); 453 454 ipmi_register_callback(NETFUN_IBM_OEM, IPMI_CMD_BMC_FACTORY_RESET, NULL, 455 ipmi_ibm_oem_bmc_factory_reset, SYSTEM_INTERFACE); 456 457 // Create new object on the bus 458 auto objPath = std::string{CONTROL_HOST_OBJ_MGR} + '/' + HOST_NAME + '0'; 459 460 // Add sdbusplus ObjectManager. 461 auto& sdbusPlusHandler = ipmid_get_sdbus_plus_handler(); 462 objManager = std::make_unique<sdbusplus::server::manager_t>( 463 *sdbusPlusHandler, CONTROL_HOST_OBJ_MGR); 464 465 opHost = std::make_unique<open_power::host::command::Host>( 466 *sdbusPlusHandler, objPath.c_str()); 467 468 // Service for this is provided by phosphor layer systemcmdintf 469 // and this will be as part of that. 470 return; 471 } 472