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