1 #include <unistd.h> 2 3 #include <attn/attn_dbus.hpp> 4 #include <attn/attn_logging.hpp> 5 #include <attn/pel/pel_minimal.hpp> 6 #include <phosphor-logging/log.hpp> 7 8 namespace attn 9 { 10 11 /** @brief Journal entry of type INFO using phosphor logging */ 12 template <> 13 void trace<INFO>(const char* i_message) 14 { 15 phosphor::logging::log<phosphor::logging::level::INFO>(i_message); 16 } 17 18 template <> 19 void trace<ERROR>(const char* i_message) 20 { 21 phosphor::logging::log<phosphor::logging::level::ERR>(i_message); 22 } 23 24 /** @brief Tuple containing information about ffdc files */ 25 using FFDCTuple = 26 std::tuple<util::FFDCFormat, uint8_t, uint8_t, sdbusplus::message::unix_fd>; 27 28 /** @brief Gather messages from the journal */ 29 std::vector<std::string> sdjGetMessages(const std::string& field, 30 const std::string& fieldValue, 31 unsigned int max); 32 33 /** 34 * Create FFDCTuple objects corresponding to the specified FFDC files. 35 * 36 * The D-Bus method to create an error log requires a vector of tuples to 37 * pass in the FFDC file information. 38 * 39 * @param files - FFDC files 40 * @return vector of FFDCTuple objects 41 */ 42 std::vector<FFDCTuple> 43 createFFDCTuples(const std::vector<util::FFDCFile>& files) 44 { 45 std::vector<FFDCTuple> ffdcTuples{}; 46 util::transformFFDC(files, ffdcTuples); 47 48 return ffdcTuples; 49 } 50 51 /** 52 * @brief Create an FFDCFile object containing raw data 53 * 54 * Throws an exception if an error occurs. 55 * 56 * @param i_buffer - raw data to add to ffdc faw data file 57 * @param i_size - size of the raw data 58 * @return FFDCFile object 59 */ 60 util::FFDCFile createFFDCRawFile(void* i_buffer, size_t i_size) 61 { 62 util::FFDCFile file{util::FFDCFormat::Custom}; 63 64 // Write buffer to file and then reset file description file offset 65 int fd = file.getFileDescriptor(); 66 size_t numBytes = write(fd, static_cast<char*>(i_buffer), i_size); 67 if (i_size != numBytes) 68 { 69 std::stringstream traceMsg; 70 traceMsg << file.getPath().c_str() << " only " << (int)numBytes 71 << " of " << (int)i_size << " bytes written"; 72 auto strobj = traceMsg.str(); 73 trace<level::ERROR>(strobj.c_str()); 74 } 75 76 lseek(fd, 0, SEEK_SET); 77 78 return file; 79 } 80 81 /** 82 * @brief Create an FFDCFile object containing the specified lines of text data 83 * 84 * Throws an exception if an error occurs. 85 * 86 * @param lines - lines of text data to write to file 87 * @return FFDCFile object 88 */ 89 util::FFDCFile createFFDCTraceFile(const std::vector<std::string>& lines) 90 { 91 // Create FFDC file of type Text 92 util::FFDCFile file{util::FFDCFormat::Text}; 93 int fd = file.getFileDescriptor(); 94 95 // Write FFDC lines to file 96 std::string buffer; 97 for (const std::string& line : lines) 98 { 99 // Copy line to buffer. Add newline if necessary. 100 buffer = line; 101 if (line.empty() || (line.back() != '\n')) 102 { 103 buffer += '\n'; 104 } 105 106 // write buffer to file 107 size_t numBytes = write(fd, buffer.c_str(), buffer.size()); 108 if (buffer.size() != numBytes) 109 { 110 std::stringstream traceMsg; 111 traceMsg << file.getPath().c_str() << " only " << (int)numBytes 112 << " of " << (int)buffer.size() << " bytes written"; 113 auto strobj = traceMsg.str(); 114 trace<level::ERROR>(strobj.c_str()); 115 } 116 } 117 118 // Seek to beginning of file so error logging system can read data 119 lseek(fd, 0, SEEK_SET); 120 121 return file; 122 } 123 124 /** 125 * Create FDDC files from journal messages of relevant executables 126 * 127 * Parse the system journal looking for log entries created by the executables 128 * of interest for logging. For each of these entries create a ffdc trace file 129 * that will be used to create ffdc log entries. These files will be pushed 130 * onto the stack of ffdc files. 131 * 132 * @param i_files - vector of ffdc files that will become log entries 133 */ 134 void createFFDCTraceFiles(std::vector<util::FFDCFile>& i_files) 135 { 136 // Executables of interest 137 std::vector<std::string> executables{"openpower-hw-diags"}; 138 139 for (const std::string& executable : executables) 140 { 141 try 142 { 143 // get journal messages 144 std::vector<std::string> messages = 145 sdjGetMessages("SYSLOG_IDENTIFIER", executable, 30); 146 147 // Create FFDC file containing the journal messages 148 if (!messages.empty()) 149 { 150 i_files.emplace_back(createFFDCTraceFile(messages)); 151 } 152 } 153 catch (const std::exception& e) 154 { 155 trace<level::INFO>("createFFDCTraceFiles exception"); 156 std::string traceMsg = std::string(e.what(), maxTraceLen); 157 trace<level::INFO>(traceMsg.c_str()); 158 } 159 } 160 } 161 162 /** 163 * Create FFDCFile objects containing debug data to store in the error log. 164 * 165 * If an error occurs, the error is written to the journal but an exception 166 * is not thrown. 167 * 168 * @param i_buffer - raw data (if creating raw dump ffdc entry in log) 169 * @return vector of FFDCFile objects 170 */ 171 std::vector<util::FFDCFile> createFFDCFiles(char* i_buffer = nullptr, 172 size_t i_size = 0) 173 { 174 std::vector<util::FFDCFile> files{}; 175 176 // Create raw dump file 177 if ((nullptr != i_buffer) && (0 != i_size)) 178 { 179 files.emplace_back(createFFDCRawFile(i_buffer, i_size)); 180 } 181 182 // Create trace dump file 183 createFFDCTraceFiles(files); 184 185 return files; 186 } 187 188 /** 189 * Create a PEL from an existing PEL 190 * 191 * Create a new PEL based on the specified raw PEL and submit the new PEL 192 * to the backend logging code as a raw PEL. Note that additional data map 193 * here contains data to be committed to the PEL and it can also be used to 194 * create the PEL as it contains needed information. 195 * 196 * @param i_buffer - buffer containing a raw PEL 197 * @param i_additional - additional data to be added to the new PEL 198 */ 199 void createPelCustom(std::vector<uint8_t>& i_rawPel, 200 std::map<std::string, std::string> i_additional) 201 { 202 // create PEL object from buffer 203 auto tiPel = std::make_unique<pel::PelMinimal>(i_rawPel); 204 205 // The additional data contains the TI info as well as the value for the 206 // subystem that provided the TI info. Get the subystem from additional 207 // data and then populate the prmary SRC and SRC words for the custom PEL 208 // based on the sybsystem's TI info. 209 uint8_t subsystem = std::stoi(i_additional["Subsystem"]); 210 tiPel->setSubsystem(subsystem); 211 212 if (static_cast<uint8_t>(pel::SubsystemID::hypervisor) == subsystem) 213 { 214 // populate hypervisor SRC words 215 tiPel->setSrcWords(std::array<uint32_t, pel::numSrcWords>{ 216 (uint32_t)std::stoul(i_additional["0x10 SRC Word 12"], 0, 16), 217 (uint32_t)std::stoul(i_additional["0x14 SRC Word 13"], 0, 16), 218 (uint32_t)std::stoul(i_additional["0x18 SRC Word 14"], 0, 16), 219 (uint32_t)std::stoul(i_additional["0x1c SRC Word 15"], 0, 16), 220 (uint32_t)std::stoul(i_additional["0x20 SRC Word 16"], 0, 16), 221 (uint32_t)std::stoul(i_additional["0x24 SRC Word 17"], 0, 16), 222 (uint32_t)std::stoul(i_additional["0x28 SRC Word 18"], 0, 16), 223 (uint32_t)std::stoul(i_additional["0x2c SRC Word 19"], 0, 16)}); 224 225 // Populate phyp primary SRC 226 227 // char array for raw pel src 228 std::array<char, pel::asciiStringSize> srcChars{'0'}; 229 230 // src from TI info 231 std::string srcString = i_additional["SrcAscii"]; 232 233 // copy from string to char array 234 srcString.copy(srcChars.data(), 235 std::min(srcString.size(), pel::asciiStringSize), 0); 236 237 tiPel->setAsciiString(srcChars); // pel object src is char array 238 239 // set symptom-id 240 auto symptomId = (i_additional["SrcAscii"].substr(0, 8) + '_'); 241 242 symptomId += (i_additional["0x10 SRC Word 12"]); 243 symptomId += (i_additional["0x14 SRC Word 13"] + '_'); 244 symptomId += (i_additional["0x18 SRC Word 14"]); 245 symptomId += (i_additional["0x1c SRC Word 15"] + '_'); 246 symptomId += (i_additional["0x20 SRC Word 16"]); 247 symptomId += (i_additional["0x24 SRC Word 17"] + '_'); 248 symptomId += (i_additional["0x28 SRC Word 18"]); 249 symptomId += (i_additional["0x2c SRC Word 19"]); 250 251 // setSymptomId will take care of required null-terminate and padding 252 tiPel->setSymptomId(symptomId); 253 } 254 else 255 { 256 // Populate hostboot SRC words - note HB word 0 from the shared info 257 // data (additional data "0x10 HB Word") is reflected in the PEL as 258 // "reason code" so we zero it here. Also note that the first word 259 // in this group of words starts at word 0 and word 1 does not exits. 260 tiPel->setSrcWords(std::array<uint32_t, pel::numSrcWords>{ 261 (uint32_t)0x00000000, 262 (uint32_t)std::stoul(i_additional["0x14 HB Word 2"], 0, 16), 263 (uint32_t)std::stoul(i_additional["0x18 HB Word 3"], 0, 16), 264 (uint32_t)std::stoul(i_additional["0x1c HB Word 4"], 0, 16), 265 (uint32_t)std::stoul(i_additional["0x20 HB Word 5"], 0, 16), 266 (uint32_t)std::stoul(i_additional["0x24 HB Word 6"], 0, 16), 267 (uint32_t)std::stoul(i_additional["0x28 HB Word 7"], 0, 16), 268 (uint32_t)std::stoul(i_additional["0x2c HB Word 8"], 0, 16)}); 269 270 // Populate hostboot primary SRC 271 272 // char array for raw pel src 273 std::array<char, pel::asciiStringSize> srcChars{'0'}; 274 275 // src from TI info 276 std::string srcString = i_additional["SrcAscii"]; 277 278 // copy from string to char array 279 srcString.copy(srcChars.data(), 280 std::min(srcString.size(), pel::asciiStringSize), 0); 281 282 tiPel->setAsciiString(srcChars); // pel object src is char array 283 284 // set symptom-id 285 auto symptomId = (i_additional["SrcAscii"].substr(0, 8) + '_'); 286 287 symptomId += (i_additional["0x10 HB Word 0"]); // note: word 1 288 symptomId += (i_additional["0x14 HB Word 2"] + '_'); // does not exist 289 symptomId += (i_additional["0x18 HB Word 3"]); 290 symptomId += (i_additional["0x1c HB Word 4"] + '_'); 291 symptomId += (i_additional["0x20 HB Word 5"]); 292 symptomId += (i_additional["0x24 HB Word 6"] + '_'); 293 symptomId += (i_additional["0x28 HB Word 7"]); 294 symptomId += (i_additional["0x2c HB Word 8"]); 295 296 // setSymptomId will take care of required null-terminate and padding 297 tiPel->setSymptomId(symptomId); 298 } 299 300 // set severity, event type and action flags 301 tiPel->setSeverity(static_cast<uint8_t>(pel::Severity::termination)); 302 tiPel->setType(static_cast<uint8_t>(pel::EventType::na)); 303 tiPel->setAction(static_cast<uint16_t>(pel::ActionFlags::service | 304 pel::ActionFlags::report | 305 pel::ActionFlags::call)); 306 307 // The raw PEL that we used as the basis for this custom PEL contains the 308 // attention handler trace data and does not needed to be in this PEL so 309 // we remove it here. 310 tiPel->setSectionCount(tiPel->getSectionCount() - 1); 311 312 // Update the raw PEL with the new custom PEL data 313 tiPel->raw(i_rawPel); 314 315 // create PEL from raw data 316 createPelRaw(i_rawPel); 317 } 318 319 /** 320 * Log an event handled by the attention handler 321 * 322 * Basic (non TI) events will generate a standard message-registry based PEL 323 * 324 * TI events will create two PEL's. One PEL will be informational and will 325 * contain trace information relevent to attention handler. The second PEL 326 * will be specific to the TI type (including the primary SRC) and will be 327 * based off of the TI information provided to the attention handler through 328 * shared TI info data area. 329 * 330 * @param i_event - The event type 331 * @param i_additional - Additional PEL data 332 * @param i_ffdc - FFDC PEL data 333 */ 334 void event(EventType i_event, std::map<std::string, std::string>& i_additional, 335 const std::vector<util::FFDCFile>& i_ffdc) 336 { 337 bool eventValid = false; // assume no event created 338 bool tiEvent = false; // assume not a terminate event 339 340 std::string eventName; 341 342 switch (i_event) 343 { 344 case EventType::Checkstop: 345 eventName = "org.open_power.HwDiags.Error.Checkstop"; 346 eventValid = true; 347 break; 348 case EventType::Terminate: 349 eventName = "org.open_power.Attn.Error.Terminate"; 350 eventValid = true; 351 tiEvent = true; 352 break; 353 case EventType::Vital: 354 eventName = "org.open_power.Attn.Error.Vital"; 355 eventValid = true; 356 break; 357 case EventType::HwDiagsFail: 358 case EventType::AttentionFail: 359 eventName = "org.open_power.Attn.Error.Fail"; 360 eventValid = true; 361 break; 362 default: 363 eventValid = false; 364 break; 365 } 366 367 if (true == eventValid) 368 { 369 // Create PEL with additional data and FFDC data. The newly created 370 // PEL's platform log-id will be returned. 371 auto pelId = 372 createPel(eventName, i_additional, createFFDCTuples(i_ffdc)); 373 374 // If this is a TI event we will create an additional PEL that is 375 // specific to the subsystem that generated the TI. 376 if ((true == tiEvent) && (0 != pelId)) 377 { 378 // get file descriptor and size of information PEL 379 int pelFd = getPel(pelId); 380 381 // if PEL found, read into buffer 382 if (-1 != pelFd) 383 { 384 auto pelSize = lseek(pelFd, 0, SEEK_END); 385 lseek(pelFd, 0, SEEK_SET); 386 387 // read information PEL into buffer 388 std::vector<uint8_t> buffer(pelSize); 389 size_t numBytes = read(pelFd, buffer.data(), buffer.size()); 390 if (buffer.size() != numBytes) 391 { 392 std::stringstream traceMsg; 393 traceMsg << "Error reading event log: " << (int)numBytes 394 << " of " << (int)buffer.size() << " bytes read"; 395 auto strobj = traceMsg.str(); 396 trace<level::ERROR>(strobj.c_str()); 397 } 398 else 399 { 400 // create PEL from buffer 401 createPelCustom(buffer, i_additional); 402 } 403 404 close(pelFd); 405 } 406 } 407 } 408 } 409 410 /** 411 * Commit special attention TI event to log 412 * 413 * Create a event log with provided additional information and standard 414 * FFDC data plus TI FFDC data 415 * 416 * @param i_additional - Additional log data 417 * @param i_ti_InfoData - TI FFDC data 418 */ 419 void eventTerminate(std::map<std::string, std::string> i_additionalData, 420 char* i_tiInfoData) 421 { 422 423 uint32_t tiInfoSize = 0; // assume TI info was not available 424 425 if (nullptr != i_tiInfoData) 426 { 427 tiInfoSize = 56; // assume not hypervisor TI 428 429 uint8_t subsystem = std::stoi(i_additionalData["Subsystem"]); 430 431 // If hypervisor 432 if (static_cast<uint8_t>(pel::SubsystemID::hypervisor) == subsystem) 433 { 434 tiInfoSize = 1024; // assume hypervisor max 435 436 // hypervisor may just want some of the data 437 if (0 == (*(i_tiInfoData + 0x09) & 0x01)) 438 { 439 uint32_t* additionalLength = (uint32_t*)(i_tiInfoData + 0x50); 440 uint32_t tiAdditional = be32toh(*additionalLength); 441 tiInfoSize = std::min(tiInfoSize, (84 + tiAdditional)); 442 } 443 } 444 } 445 446 std::string traceMsg = "TI info size = " + std::to_string(tiInfoSize); 447 trace<level::INFO>(traceMsg.c_str()); 448 449 event(EventType::Terminate, i_additionalData, 450 createFFDCFiles(i_tiInfoData, tiInfoSize)); 451 } 452 453 /** @brief Commit SBE vital event to log */ 454 void eventVital() 455 { 456 // Additional data for log 457 std::map<std::string, std::string> additionalData; 458 459 // Create log event with additional data and FFDC data 460 event(EventType::Vital, additionalData, createFFDCFiles(nullptr, 0)); 461 } 462 463 /** 464 * Commit attention handler failure event to log 465 * 466 * Create an event log containing the specified error code. 467 * 468 * @param i_error - Error code 469 */ 470 void eventAttentionFail(int i_error) 471 { 472 // Additional data for log 473 std::map<std::string, std::string> additionalData; 474 additionalData["ERROR_CODE"] = std::to_string(i_error); 475 476 // Create log event with additional data and FFDC data 477 event(EventType::AttentionFail, additionalData, 478 createFFDCFiles(nullptr, 0)); 479 } 480 481 /** 482 * Parse systemd journal message field 483 * 484 * Parse the journal looking for the specified field and return the journal 485 * data for that field. 486 * 487 * @param journal - The journal to parse 488 * @param field - Field containing the data to retrieve 489 * @return Data for the speciefied field 490 */ 491 std::string sdjGetFieldValue(sd_journal* journal, const char* field) 492 { 493 const char* data{nullptr}; 494 size_t length{0}; 495 496 // get field value 497 if (0 == sd_journal_get_data(journal, field, (const void**)&data, &length)) 498 { 499 size_t prefix{0}; 500 501 // The data returned by sd_journal_get_data will be prefixed with the 502 // field name and "=" 503 const void* eq = memchr(data, '=', length); 504 if (nullptr != eq) 505 { 506 // get just data following the "=" 507 prefix = (const char*)eq - data + 1; 508 } 509 else 510 { 511 // all the data (should not happen) 512 prefix = 0; 513 std::string value{}; // empty string 514 } 515 516 return std::string{data + prefix, length - prefix}; 517 } 518 else 519 { 520 return std::string{}; // empty string 521 } 522 } 523 524 /** 525 * Gather messages from the journal 526 * 527 * Fetch journal entry data for all entries with the specified field equal to 528 * the specified value. 529 * 530 * @param field - Field to search on 531 * @param fieldValue - Value to search for 532 * @param max - Maximum number of messages fetch 533 * @return Vector of journal entry data 534 */ 535 std::vector<std::string> sdjGetMessages(const std::string& field, 536 const std::string& fieldValue, 537 unsigned int max) 538 { 539 sd_journal* journal; 540 std::vector<std::string> messages; 541 542 if (0 == sd_journal_open(&journal, SD_JOURNAL_LOCAL_ONLY)) 543 { 544 SD_JOURNAL_FOREACH_BACKWARDS(journal) 545 { 546 // Get input field 547 std::string value = sdjGetFieldValue(journal, field.c_str()); 548 549 // Compare field value and read data 550 if (value == fieldValue) 551 { 552 // Get SYSLOG_IDENTIFIER field (process that logged message) 553 std::string syslog = 554 sdjGetFieldValue(journal, "SYSLOG_IDENTIFIER"); 555 556 // Get _PID field 557 std::string pid = sdjGetFieldValue(journal, "_PID"); 558 559 // Get MESSAGE field 560 std::string message = sdjGetFieldValue(journal, "MESSAGE"); 561 562 // Get timestamp 563 uint64_t usec{0}; 564 if (0 == sd_journal_get_realtime_usec(journal, &usec)) 565 { 566 567 // Convert realtime microseconds to date format 568 char dateBuffer[80]; 569 std::string date; 570 std::time_t timeInSecs = usec / 1000000; 571 strftime(dateBuffer, sizeof(dateBuffer), "%b %d %H:%M:%S", 572 std::localtime(&timeInSecs)); 573 date = dateBuffer; 574 575 // Store value to messages 576 value = date + " " + syslog + "[" + pid + "]: " + message; 577 messages.insert(messages.begin(), value); 578 } 579 } 580 581 // limit maximum number of messages 582 if (messages.size() >= max) 583 { 584 break; 585 } 586 } 587 588 sd_journal_close(journal); // close journal when done 589 } 590 591 return messages; 592 } 593 594 } // namespace attn 595