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