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