xref: /openbmc/fb-ipmi-oem/src/selcommands.cpp (revision 010dee04)
1 /*
2  * Copyright (c)  2018 Intel Corporation.
3  * Copyright (c)  2018-present Facebook.
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17 
18 #include <boost/algorithm/string/join.hpp>
19 #include <boost/container/flat_map.hpp>
20 #include <ipmid/api.hpp>
21 #include <nlohmann/json.hpp>
22 #include <phosphor-logging/log.hpp>
23 #include <sdbusplus/message/types.hpp>
24 #include <sdbusplus/timer.hpp>
25 #include <storagecommands.hpp>
26 
27 #include <fstream>
28 #include <iostream>
29 #include <sstream>
30 
31 enum class MemErrType
32 {
33     memTrainErr = 0,
34     memPmicErr = 7
35 };
36 
37 enum class PostEvtType
38 {
39     pxeBootFail = 0,
40     httpBootFail = 6,
41     getCertFail = 7,
42     amdAblFail = 10
43 };
44 
45 enum class PcieEvtType
46 {
47     dpc = 0
48 };
49 
50 enum class MemEvtType
51 {
52     ppr = 0,
53     adddc = 5,
54     noDimm = 7
55 };
56 
57 //----------------------------------------------------------------------
58 // Platform specific functions for storing app data
59 //----------------------------------------------------------------------
60 
61 static std::string byteToStr(uint8_t byte)
62 {
63     std::stringstream ss;
64 
65     ss << std::hex << std::uppercase << std::setfill('0');
66     ss << std::setw(2) << (int)byte;
67 
68     return ss.str();
69 }
70 
71 static void toHexStr(std::vector<uint8_t>& bytes, std::string& hexStr)
72 {
73     std::stringstream stream;
74     stream << std::hex << std::uppercase << std::setfill('0');
75     for (const uint8_t byte : bytes)
76     {
77         stream << std::setw(2) << static_cast<int>(byte);
78     }
79     hexStr = stream.str();
80 }
81 
82 static int fromHexStr(const std::string hexStr, std::vector<uint8_t>& data)
83 {
84     for (unsigned int i = 0; i < hexStr.size(); i += 2)
85     {
86         try
87         {
88             data.push_back(static_cast<uint8_t>(
89                 std::stoul(hexStr.substr(i, 2), nullptr, 16)));
90         }
91         catch (const std::invalid_argument& e)
92         {
93             phosphor::logging::log<phosphor::logging::level::ERR>(e.what());
94             return -1;
95         }
96         catch (const std::out_of_range& e)
97         {
98             phosphor::logging::log<phosphor::logging::level::ERR>(e.what());
99             return -1;
100         }
101     }
102     return 0;
103 }
104 
105 namespace fb_oem::ipmi::sel
106 {
107 
108 class SELData
109 {
110   private:
111     nlohmann::json selDataObj;
112 
113     void flush()
114     {
115         std::ofstream file(SEL_JSON_DATA_FILE);
116         file << selDataObj;
117         file.close();
118     }
119 
120     void init()
121     {
122         selDataObj[KEY_SEL_VER] = 0x51;
123         selDataObj[KEY_SEL_COUNT] = 0;
124         selDataObj[KEY_ADD_TIME] = 0xFFFFFFFF;
125         selDataObj[KEY_ERASE_TIME] = 0xFFFFFFFF;
126         selDataObj[KEY_OPER_SUPP] = 0x02;
127         /* Spec indicates that more than 64kB is free */
128         selDataObj[KEY_FREE_SPACE] = 0xFFFF;
129     }
130 
131   public:
132     SELData()
133     {
134         /* Get App data stored in json file */
135         std::ifstream file(SEL_JSON_DATA_FILE);
136         if (file)
137         {
138             file >> selDataObj;
139             file.close();
140         }
141 
142         /* Initialize SelData object if no entries. */
143         if (selDataObj.find(KEY_SEL_COUNT) == selDataObj.end())
144         {
145             init();
146         }
147     }
148 
149     int clear()
150     {
151         /* Clear the complete Sel Json object */
152         selDataObj.clear();
153         /* Reinitialize it with basic data */
154         init();
155         /* Save the erase time */
156         struct timespec selTime = {};
157         if (clock_gettime(CLOCK_REALTIME, &selTime) < 0)
158         {
159             return -1;
160         }
161         selDataObj[KEY_ERASE_TIME] = selTime.tv_sec;
162         flush();
163         return 0;
164     }
165 
166     uint32_t getCount()
167     {
168         return selDataObj[KEY_SEL_COUNT];
169     }
170 
171     void getInfo(GetSELInfoData& info)
172     {
173         info.selVersion = selDataObj[KEY_SEL_VER];
174         info.entries = selDataObj[KEY_SEL_COUNT];
175         info.freeSpace = selDataObj[KEY_FREE_SPACE];
176         info.addTimeStamp = selDataObj[KEY_ADD_TIME];
177         info.eraseTimeStamp = selDataObj[KEY_ERASE_TIME];
178         info.operationSupport = selDataObj[KEY_OPER_SUPP];
179     }
180 
181     int getEntry(uint32_t index, std::string& rawStr)
182     {
183         std::stringstream ss;
184         ss << std::hex;
185         ss << std::setw(2) << std::setfill('0') << index;
186 
187         /* Check or the requested SEL Entry, if record is available */
188         if (selDataObj.find(ss.str()) == selDataObj.end())
189         {
190             return -1;
191         }
192 
193         rawStr = selDataObj[ss.str()][KEY_SEL_ENTRY_RAW];
194         return 0;
195     }
196 
197     int addEntry(std::string keyStr)
198     {
199         struct timespec selTime = {};
200 
201         if (clock_gettime(CLOCK_REALTIME, &selTime) < 0)
202         {
203             return -1;
204         }
205 
206         selDataObj[KEY_ADD_TIME] = selTime.tv_sec;
207 
208         int selCount = selDataObj[KEY_SEL_COUNT];
209         selDataObj[KEY_SEL_COUNT] = ++selCount;
210 
211         std::stringstream ss;
212         ss << std::hex;
213         ss << std::setw(2) << std::setfill('0') << selCount;
214 
215         selDataObj[ss.str()][KEY_SEL_ENTRY_RAW] = keyStr;
216         flush();
217         return selCount;
218     }
219 };
220 
221 /*
222  * A Function to parse common SEL message, a helper function
223  * for parseStdSel.
224  *
225  * Note that this function __CANNOT__ be overridden.
226  * To add board specific routine, please override parseStdSel.
227  */
228 
229 /*Used by decoding ME event*/
230 std::vector<std::string> nmDomName = {
231     "Entire Platform",          "CPU Subsystem",
232     "Memory Subsystem",         "HW Protection",
233     "High Power I/O subsystem", "Unknown"};
234 
235 /* Default log message for unknown type */
236 static void logDefault(uint8_t*, std::string& errLog)
237 {
238     errLog = "Unknown";
239 }
240 
241 static void logSysEvent(uint8_t* data, std::string& errLog)
242 {
243     if (data[0] == 0xE5)
244     {
245         errLog = "Cause of Time change - ";
246         switch (data[2])
247         {
248             case 0x00:
249                 errLog += "NTP";
250                 break;
251             case 0x01:
252                 errLog += "Host RTL";
253                 break;
254             case 0x02:
255                 errLog += "Set SEL time cmd";
256                 break;
257             case 0x03:
258                 errLog += "Set SEL time UTC offset cmd";
259                 break;
260             default:
261                 errLog += "Unknown";
262         }
263 
264         if (data[1] == 0x00)
265             errLog += " - First Time";
266         else if (data[1] == 0x80)
267             errLog += " - Second Time";
268     }
269     else
270     {
271         errLog = "Unknown";
272     }
273 }
274 
275 static void logThermalEvent(uint8_t* data, std::string& errLog)
276 {
277     if (data[0] == 0x1)
278     {
279         errLog = "Limit Exceeded";
280     }
281     else
282     {
283         errLog = "Unknown";
284     }
285 }
286 
287 static void logCritIrq(uint8_t* data, std::string& errLog)
288 {
289     if (data[0] == 0x0)
290     {
291         errLog = "NMI / Diagnostic Interrupt";
292     }
293     else if (data[0] == 0x03)
294     {
295         errLog = "Software NMI";
296     }
297     else
298     {
299         errLog = "Unknown";
300     }
301 
302     /* TODO: Call add_cri_sel for CRITICAL_IRQ */
303 }
304 
305 static void logPostErr(uint8_t* data, std::string& errLog)
306 {
307     if ((data[0] & 0x0F) == 0x0)
308     {
309         errLog = "System Firmware Error";
310     }
311     else
312     {
313         errLog = "Unknown";
314     }
315 
316     if (((data[0] >> 6) & 0x03) == 0x3)
317     {
318         // TODO: Need to implement IPMI spec based Post Code
319         errLog += ", IPMI Post Code";
320     }
321     else if (((data[0] >> 6) & 0x03) == 0x2)
322     {
323         errLog += ", OEM Post Code 0x" + byteToStr(data[2]) +
324                   byteToStr(data[1]);
325 
326         switch ((data[2] << 8) | data[1])
327         {
328             case 0xA105:
329                 errLog += ", BMC Failed (No Response)";
330                 break;
331             case 0xA106:
332                 errLog += ", BMC Failed (Self Test Fail)";
333                 break;
334             case 0xA10A:
335                 errLog += ", System Firmware Corruption Detected";
336                 break;
337             case 0xA10B:
338                 errLog += ", TPM Self-Test FAIL Detected";
339         }
340     }
341 }
342 
343 static void logMchChkErr(uint8_t* data, std::string& errLog)
344 {
345     /* TODO: Call add_cri_sel for CRITICAL_IRQ */
346     if ((data[0] & 0x0F) == 0x0B)
347     {
348         errLog = "Uncorrectable";
349     }
350     else if ((data[0] & 0x0F) == 0x0C)
351     {
352         errLog = "Correctable";
353     }
354     else
355     {
356         errLog = "Unknown";
357     }
358 
359     errLog += ", Machine Check bank Number " + std::to_string(data[1]) +
360               ", CPU " + std::to_string(data[2] >> 5) + ", Core " +
361               std::to_string(data[2] & 0x1F);
362 }
363 
364 static void logPcieErr(uint8_t* data, std::string& errLog)
365 {
366     std::stringstream tmp1, tmp2;
367     tmp1 << std::hex << std::uppercase << std::setfill('0');
368     tmp2 << std::hex << std::uppercase << std::setfill('0');
369     tmp1 << " (Bus " << std::setw(2) << (int)(data[2]) << " / Dev "
370          << std::setw(2) << (int)(data[1] >> 3) << " / Fun " << std::setw(2)
371          << (int)(data[1] & 0x7) << ")";
372 
373     switch (data[0] & 0xF)
374     {
375         case 0x4:
376             errLog = "PCI PERR" + tmp1.str();
377             break;
378         case 0x5:
379             errLog = "PCI SERR" + tmp1.str();
380             break;
381         case 0x7:
382             errLog = "Correctable" + tmp1.str();
383             break;
384         case 0x8:
385             errLog = "Uncorrectable" + tmp1.str();
386             break;
387         case 0xA:
388             errLog = "Bus Fatal" + tmp1.str();
389             break;
390         case 0xD:
391         {
392             uint32_t venId = (uint32_t)data[1] << 8 | (uint32_t)data[2];
393             tmp2 << "Vendor ID: 0x" << std::setw(4) << venId;
394             errLog = tmp2.str();
395         }
396         break;
397         case 0xE:
398         {
399             uint32_t devId = (uint32_t)data[1] << 8 | (uint32_t)data[2];
400             tmp2 << "Device ID: 0x" << std::setw(4) << devId;
401             errLog = tmp2.str();
402         }
403         break;
404         case 0xF:
405             tmp2 << "Error ID from downstream: 0x" << std::setw(2)
406                  << (int)(data[1]) << std::setw(2) << (int)(data[2]);
407             errLog = tmp2.str();
408             break;
409         default:
410             errLog = "Unknown";
411     }
412 }
413 
414 static void logIioErr(uint8_t* data, std::string& errLog)
415 {
416     std::vector<std::string> tmpStr = {
417         "IRP0", "IRP1", " IIO-Core", "VT-d", "Intel Quick Data",
418         "Misc", " DMA", "ITC",       "OTC",  "CI"};
419 
420     if ((data[0] & 0xF) == 0)
421     {
422         errLog += "CPU " + std::to_string(data[2] >> 5) + ", Error ID 0x" +
423                   byteToStr(data[1]) + " - ";
424 
425         if ((data[2] & 0xF) <= 0x9)
426         {
427             errLog += tmpStr[(data[2] & 0xF)];
428         }
429         else
430         {
431             errLog += "Reserved";
432         }
433     }
434     else
435     {
436         errLog = "Unknown";
437     }
438 }
439 
440 [[maybe_unused]] static void logMemErr(uint8_t* dataPtr, std::string& errLog)
441 {
442     uint8_t snrType = dataPtr[0];
443     uint8_t snrNum = dataPtr[1];
444     uint8_t* data = &(dataPtr[3]);
445 
446     /* TODO: add pal_add_cri_sel */
447 
448     if (snrNum == memoryEccError)
449     {
450         /* SEL from MEMORY_ECC_ERR Sensor */
451         switch (data[0] & 0x0F)
452         {
453             case 0x0:
454                 if (snrType == 0x0C)
455                 {
456                     errLog = "Correctable";
457                 }
458                 else if (snrType == 0x10)
459                 {
460                     errLog = "Correctable ECC error Logging Disabled";
461                 }
462                 break;
463             case 0x1:
464                 errLog = "Uncorrectable";
465                 break;
466             case 0x5:
467                 errLog = "Correctable ECC error Logging Limit Disabled";
468                 break;
469             default:
470                 errLog = "Unknown";
471         }
472     }
473     else if (snrNum == memoryErrLogDIS)
474     {
475         // SEL from MEMORY_ERR_LOG_DIS Sensor
476         if ((data[0] & 0x0F) == 0x0)
477         {
478             errLog = "Correctable Memory Error Logging Disabled";
479         }
480         else
481         {
482             errLog = "Unknown";
483         }
484     }
485     else
486     {
487         errLog = "Unknown";
488         return;
489     }
490 
491     /* Common routine for both MEM_ECC_ERR and MEMORY_ERR_LOG_DIS */
492 
493     errLog += " (DIMM " + byteToStr(data[2]) + ") Logical Rank " +
494               std::to_string(data[1] & 0x03);
495 
496     /* DIMM number (data[2]):
497      * Bit[7:5]: Socket number  (Range: 0-7)
498      * Bit[4:3]: Channel number (Range: 0-3)
499      * Bit[2:0]: DIMM number    (Range: 0-7)
500      */
501 
502     /* TODO: Verify these bits */
503     std::string cpuStr = "CPU# " + std::to_string((data[2] & 0xE0) >> 5);
504     std::string chStr = "CHN# " + std::to_string((data[2] & 0x18) >> 3);
505     std::string dimmStr = "DIMM#" + std::to_string(data[2] & 0x7);
506 
507     switch ((data[1] & 0xC) >> 2)
508     {
509         case 0x0:
510         {
511             /* All Info Valid */
512             [[maybe_unused]] uint8_t chnNum = (data[2] & 0x1C) >> 2;
513             [[maybe_unused]] uint8_t dimmNum = data[2] & 0x3;
514 
515             /* TODO: If critical SEL logging is available, do it */
516             if (snrType == 0x0C)
517             {
518                 if ((data[0] & 0x0F) == 0x0)
519                 {
520                     /* TODO: add_cri_sel */
521                     /* "DIMM"+ 'A'+ chnNum + dimmNum + " ECC err,FRU:1"
522                      */
523                 }
524                 else if ((data[0] & 0x0F) == 0x1)
525                 {
526                     /* TODO: add_cri_sel */
527                     /* "DIMM"+ 'A'+ chnNum + dimmNum + " UECC err,FRU:1"
528                      */
529                 }
530             }
531             /* Continue to parse the error into a string. All Info Valid
532              */
533             errLog += " (" + cpuStr + ", " + chStr + ", " + dimmStr + ")";
534         }
535 
536         break;
537         case 0x1:
538 
539             /* DIMM info not valid */
540             errLog += " (" + cpuStr + ", " + chStr + ")";
541             break;
542         case 0x2:
543 
544             /* CHN info not valid */
545             errLog += " (" + cpuStr + ", " + dimmStr + ")";
546             break;
547         case 0x3:
548 
549             /* CPU info not valid */
550             errLog += " (" + chStr + ", " + dimmStr + ")";
551             break;
552     }
553 }
554 
555 static void logPwrErr(uint8_t* data, std::string& errLog)
556 {
557     if (data[0] == 0x1)
558     {
559         errLog = "SYS_PWROK failure";
560         /* Also try logging to Critical log file, if available */
561         /* "SYS_PWROK failure,FRU:1" */
562     }
563     else if (data[0] == 0x2)
564     {
565         errLog = "PCH_PWROK failure";
566         /* Also try logging to Critical log file, if available */
567         /* "PCH_PWROK failure,FRU:1" */
568     }
569     else
570     {
571         errLog = "Unknown";
572     }
573 }
574 
575 static void logCatErr(uint8_t* data, std::string& errLog)
576 {
577     if (data[0] == 0x0)
578     {
579         errLog = "IERR/CATERR";
580         /* Also try logging to Critical log file, if available */
581         /* "IERR,FRU:1 */
582     }
583     else if (data[0] == 0xB)
584     {
585         errLog = "MCERR/CATERR";
586         /* Also try logging to Critical log file, if available */
587         /* "MCERR,FRU:1 */
588     }
589     else
590     {
591         errLog = "Unknown";
592     }
593 }
594 
595 static void logDimmHot(uint8_t* data, std::string& errLog)
596 {
597     if ((data[0] << 16 | data[1] << 8 | data[2]) == 0x01FFFF)
598     {
599         errLog = "SOC MEMHOT";
600     }
601     else
602     {
603         errLog = "Unknown";
604         /* Also try logging to Critical log file, if available */
605         /* ""CPU_DIMM_HOT %s,FRU:1" */
606     }
607 }
608 
609 static void logSwNMI(uint8_t* data, std::string& errLog)
610 {
611     if ((data[0] << 16 | data[1] << 8 | data[2]) == 0x03FFFF)
612     {
613         errLog = "Software NMI";
614     }
615     else
616     {
617         errLog = "Unknown SW NMI";
618     }
619 }
620 
621 static void logCPUThermalSts(uint8_t* data, std::string& errLog)
622 {
623     switch (data[0])
624     {
625         case 0x0:
626             errLog = "CPU Critical Temperature";
627             break;
628         case 0x1:
629             errLog = "PROCHOT#";
630             break;
631         case 0x2:
632             errLog = "TCC Activation";
633             break;
634         default:
635             errLog = "Unknown";
636     }
637 }
638 
639 static void logMEPwrState(uint8_t* data, std::string& errLog)
640 {
641     switch (data[0])
642     {
643         case 0:
644             errLog = "RUNNING";
645             break;
646         case 2:
647             errLog = "POWER_OFF";
648             break;
649         default:
650             errLog = "Unknown[" + std::to_string(data[0]) + "]";
651             break;
652     }
653 }
654 
655 static void logSPSFwHealth(uint8_t* data, std::string& errLog)
656 {
657     if ((data[0] & 0x0F) == 0x00)
658     {
659         const std::vector<std::string> tmpStr = {
660             "Recovery GPIO forced",
661             "Image execution failed",
662             "Flash erase error",
663             "Flash state information",
664             "Internal error",
665             "BMC did not respond",
666             "Direct Flash update",
667             "Manufacturing error",
668             "Automatic Restore to Factory Presets",
669             "Firmware Exception",
670             "Flash Wear-Out Protection Warning",
671             "Unknown",
672             "Unknown",
673             "DMI interface error",
674             "MCTP interface error",
675             "Auto-configuration finished",
676             "Unsupported Segment Defined Feature",
677             "Unknown",
678             "CPU Debug Capability Disabled",
679             "UMA operation error"};
680 
681         if (data[1] < 0x14)
682         {
683             errLog = tmpStr[data[1]];
684         }
685         else
686         {
687             errLog = "Unknown";
688         }
689     }
690     else if ((data[0] & 0x0F) == 0x01)
691     {
692         errLog = "SMBus link failure";
693     }
694     else
695     {
696         errLog = "Unknown";
697     }
698 }
699 
700 static void logNmExcA(uint8_t* data, std::string& errLog)
701 {
702     /*NM4.0 #550710, Revision 1.95, and turn to p.155*/
703     if (data[0] == 0xA8)
704     {
705         errLog = "Policy Correction Time Exceeded";
706     }
707     else
708     {
709         errLog = "Unknown";
710     }
711 }
712 
713 static void logPCHThermal(uint8_t* data, std::string& errLog)
714 {
715     const std::vector<std::string> thresEvtName = {
716         "Lower Non-critical",
717         "Unknown",
718         "Lower Critical",
719         "Unknown",
720         "Lower Non-recoverable",
721         "Unknown",
722         "Unknown",
723         "Upper Non-critical",
724         "Unknown",
725         "Upper Critical",
726         "Unknown",
727         "Upper Non-recoverable"};
728 
729     if ((data[0] & 0x0f) < 12)
730     {
731         errLog = thresEvtName[(data[0] & 0x0f)];
732     }
733     else
734     {
735         errLog = "Unknown";
736     }
737 
738     errLog += ", curr_val: " + std::to_string(data[1]) +
739               " C, thresh_val: " + std::to_string(data[2]) + " C";
740 }
741 
742 static void logNmHealth(uint8_t* data, std::string& errLog)
743 {
744     std::vector<std::string> nmErrType = {
745         "Unknown",
746         "Unknown",
747         "Unknown",
748         "Unknown",
749         "Unknown",
750         "Unknown",
751         "Unknown",
752         "Extended Telemetry Device Reading Failure",
753         "Outlet Temperature Reading Failure",
754         "Volumetric Airflow Reading Failure",
755         "Policy Misconfiguration",
756         "Power Sensor Reading Failure",
757         "Inlet Temperature Reading Failure",
758         "Host Communication Error",
759         "Real-time Clock Synchronization Failure",
760         "Platform Shutdown Initiated by Intel NM Policy",
761         "Unknown"};
762     uint8_t nmTypeIdx = (data[0] & 0xf);
763     uint8_t domIdx = (data[1] & 0xf);
764     uint8_t errIdx = ((data[1] >> 4) & 0xf);
765 
766     if (nmTypeIdx == 2)
767     {
768         errLog = "SensorIntelNM";
769     }
770     else
771     {
772         errLog = "Unknown";
773     }
774 
775     errLog += ", Domain:" + nmDomName[domIdx] + ", ErrType:" +
776               nmErrType[errIdx] + ", Err:0x" + byteToStr(data[2]);
777 }
778 
779 static void logNmCap(uint8_t* data, std::string& errLog)
780 {
781     const std::vector<std::string> nmCapStsStr = {"Not Available", "Available"};
782     if (data[0] & 0x7) // BIT1=policy, BIT2=monitoring, BIT3=pwr
783                        // limit and the others are reserved
784     {
785         errLog = "PolicyInterface:" + nmCapStsStr[BIT(data[0], 0)] +
786                  ",Monitoring:" + nmCapStsStr[BIT(data[0], 1)] +
787                  ",PowerLimit:" + nmCapStsStr[BIT(data[0], 2)];
788     }
789     else
790     {
791         errLog = "Unknown";
792     }
793 }
794 
795 static void logNmThreshold(uint8_t* data, std::string& errLog)
796 {
797     uint8_t thresNum = (data[0] & 0x3);
798     uint8_t domIdx = (data[1] & 0xf);
799     uint8_t polId = data[2];
800     uint8_t polEvtIdx = BIT(data[0], 3);
801     const std::vector<std::string> polEvtStr = {
802         "Threshold Exceeded", "Policy Correction Time Exceeded"};
803 
804     errLog = "Threshold Number:" + std::to_string(thresNum) + "-" +
805              polEvtStr[polEvtIdx] + ", Domain:" + nmDomName[domIdx] +
806              ", PolicyID:0x" + byteToStr(polId);
807 }
808 
809 static void logPwrThreshold(uint8_t* data, std::string& errLog)
810 {
811     if (data[0] == 0x00)
812     {
813         errLog = "Limit Not Exceeded";
814     }
815     else if (data[0] == 0x01)
816     {
817         errLog = "Limit Exceeded";
818     }
819     else
820     {
821         errLog = "Unknown";
822     }
823 }
824 
825 static void logMSMI(uint8_t* data, std::string& errLog)
826 {
827     if (data[0] == 0x0)
828     {
829         errLog = "IERR/MSMI";
830     }
831     else if (data[0] == 0x0B)
832     {
833         errLog = "MCERR/MSMI";
834     }
835     else
836     {
837         errLog = "Unknown";
838     }
839 }
840 
841 static void logHprWarn(uint8_t* data, std::string& errLog)
842 {
843     if (data[2] == 0x01)
844     {
845         if (data[1] == 0xFF)
846         {
847             errLog = "Infinite Time";
848         }
849         else
850         {
851             errLog = std::to_string(data[1]) + " minutes";
852         }
853     }
854     else
855     {
856         errLog = "Unknown";
857     }
858 }
859 
860 static const boost::container::flat_map<
861     uint8_t,
862     std::pair<std::string, std::function<void(uint8_t*, std::string&)>>>
863     sensorNameTable = {
864         {0xE9, {"SYSTEM_EVENT", logSysEvent}},
865         {0x7D, {"THERM_THRESH_EVT", logThermalEvent}},
866         {0xAA, {"BUTTON", logDefault}},
867         {0xAB, {"POWER_STATE", logDefault}},
868         {0xEA, {"CRITICAL_IRQ", logCritIrq}},
869         {0x2B, {"POST_ERROR", logPostErr}},
870         {0x40, {"MACHINE_CHK_ERR", logMchChkErr}},
871         {0x41, {"PCIE_ERR", logPcieErr}},
872         {0x43, {"IIO_ERR", logIioErr}},
873         {0X63, {"MEMORY_ECC_ERR", logDefault}},
874         {0X87, {"MEMORY_ERR_LOG_DIS", logDefault}},
875         {0X51, {"PROCHOT_EXT", logDefault}},
876         {0X56, {"PWR_ERR", logPwrErr}},
877         {0xE6, {"CATERR_A", logCatErr}},
878         {0xEB, {"CATERR_B", logCatErr}},
879         {0xB3, {"CPU_DIMM_HOT", logDimmHot}},
880         {0x90, {"SOFTWARE_NMI", logSwNMI}},
881         {0x1C, {"CPU0_THERM_STATUS", logCPUThermalSts}},
882         {0x1D, {"CPU1_THERM_STATUS", logCPUThermalSts}},
883         {0x16, {"ME_POWER_STATE", logMEPwrState}},
884         {0x17, {"SPS_FW_HEALTH", logSPSFwHealth}},
885         {0x18, {"NM_EXCEPTION_A", logNmExcA}},
886         {0x08, {"PCH_THERM_THRESHOLD", logPCHThermal}},
887         {0x19, {"NM_HEALTH", logNmHealth}},
888         {0x1A, {"NM_CAPABILITIES", logNmCap}},
889         {0x1B, {"NM_THRESHOLD", logNmThreshold}},
890         {0x3B, {"PWR_THRESH_EVT", logPwrThreshold}},
891         {0xE7, {"MSMI", logMSMI}},
892         {0xC5, {"HPR_WARNING", logHprWarn}}};
893 
894 static void parseSelHelper(StdSELEntry* data, std::string& errStr)
895 {
896     /* Check if sensor type is OS_BOOT (0x1f) */
897     if (data->sensorType == 0x1F)
898     {
899         /* OS_BOOT used by OS */
900         switch (data->eventData1 & 0xF)
901         {
902             case 0x07:
903                 errStr = "Base OS/Hypervisor Installation started";
904                 break;
905             case 0x08:
906                 errStr = "Base OS/Hypervisor Installation completed";
907                 break;
908             case 0x09:
909                 errStr = "Base OS/Hypervisor Installation aborted";
910                 break;
911             case 0x0A:
912                 errStr = "Base OS/Hypervisor Installation failed";
913                 break;
914             default:
915                 errStr = "Unknown";
916         }
917         return;
918     }
919 
920     auto findSensorName = sensorNameTable.find(data->sensorNum);
921     if (findSensorName == sensorNameTable.end())
922     {
923         errStr = "Unknown";
924         return;
925     }
926     else
927     {
928         switch (data->sensorNum)
929         {
930             /* logMemErr function needs data from sensor type */
931             case memoryEccError:
932             case memoryErrLogDIS:
933                 findSensorName->second.second(&(data->sensorType), errStr);
934                 break;
935             /* Other sensor function needs only event data for parsing */
936             default:
937                 findSensorName->second.second(&(data->eventData1), errStr);
938         }
939     }
940 
941     if (((data->eventData3 & 0x80) >> 7) == 0)
942     {
943         errStr += " Assertion";
944     }
945     else
946     {
947         errStr += " Deassertion";
948     }
949 }
950 
951 static void parseDimmPhyloc(StdSELEntry* data, std::string& errStr)
952 {
953     // Log when " All info available"
954     uint8_t chNum = (data->eventData3 & 0x18) >> 3;
955     uint8_t dimmNum = data->eventData3 & 0x7;
956     uint8_t rankNum = data->eventData2 & 0x03;
957     uint8_t nodeNum = (data->eventData3 & 0xE0) >> 5;
958 
959     if (chNum == 3 && dimmNum == 0)
960     {
961         errStr += " Node: " + std::to_string(nodeNum) + "," +
962                   " Card: " + std::to_string(chNum) + "," +
963                   " Module: " + std::to_string(dimmNum) + "," +
964                   " Rank Number: " + std::to_string(rankNum) + "," +
965                   "  Location: DIMM A0";
966     }
967     else if (chNum == 2 && dimmNum == 0)
968     {
969         errStr += " Node: " + std::to_string(nodeNum) + "," +
970                   " Card: " + std::to_string(chNum) + "," +
971                   " Module: " + std::to_string(dimmNum) + "," +
972                   " Rank Number: " + std::to_string(rankNum) + "," +
973                   " Location: DIMM B0";
974     }
975     else if (chNum == 4 && dimmNum == 0)
976     {
977         errStr += " Node: " + std::to_string(nodeNum) + "," +
978                   " Card: " + std::to_string(chNum) + "," +
979                   " Module: " + std::to_string(dimmNum) + "," +
980                   " Rank Number: " + std::to_string(rankNum) + "," +
981                   " Location: DIMM C0 ";
982     }
983     else if (chNum == 5 && dimmNum == 0)
984     {
985         errStr += " Node: " + std::to_string(nodeNum) + "," +
986                   " Card: " + std::to_string(chNum) + "," +
987                   " Module: " + std::to_string(dimmNum) + "," +
988                   " Rank Number: " + std::to_string(rankNum) + "," +
989                   " Location: DIMM D0";
990     }
991     else
992     {
993         errStr += " Node: " + std::to_string(nodeNum) + "," +
994                   " Card: " + std::to_string(chNum) + "," +
995                   " Module: " + std::to_string(dimmNum) + "," +
996                   " Rank Number: " + std::to_string(rankNum) + "," +
997                   " Location: DIMM Unknown";
998     }
999 }
1000 
1001 static void parseStdSel(StdSELEntry* data, std::string& errStr)
1002 {
1003     std::stringstream tmpStream;
1004     tmpStream << std::hex << std::uppercase;
1005 
1006     /* TODO: add pal_add_cri_sel */
1007     switch (data->sensorNum)
1008     {
1009         case memoryEccError:
1010             switch (data->eventData1 & 0x0F)
1011             {
1012                 case 0x00:
1013                     errStr = "Correctable";
1014                     tmpStream << "DIMM" << std::setw(2) << std::setfill('0')
1015                               << data->eventData3 << " ECC err";
1016                     parseDimmPhyloc(data, errStr);
1017                     break;
1018                 case 0x01:
1019                     errStr = "Uncorrectable";
1020                     tmpStream << "DIMM" << std::setw(2) << std::setfill('0')
1021                               << data->eventData3 << " UECC err";
1022                     parseDimmPhyloc(data, errStr);
1023                     break;
1024                 case 0x02:
1025                     errStr = "Parity";
1026                     break;
1027                 case 0x05:
1028                     errStr = "Correctable ECC error Logging Limit Reached";
1029                     break;
1030                 default:
1031                     errStr = "Unknown";
1032             }
1033             break;
1034         case memoryErrLogDIS:
1035             if ((data->eventData1 & 0x0F) == 0)
1036             {
1037                 errStr = "Correctable Memory Error Logging Disabled";
1038             }
1039             else
1040             {
1041                 errStr = "Unknown";
1042             }
1043             break;
1044         default:
1045             parseSelHelper(data, errStr);
1046             return;
1047     }
1048 
1049     errStr += " (DIMM " + std::to_string(data->eventData3) + ")";
1050     errStr += " Logical Rank " + std::to_string(data->eventData2 & 0x03);
1051 
1052     switch ((data->eventData2 & 0x0C) >> 2)
1053     {
1054         case 0x00:
1055             // Ignore when " All info available"
1056             break;
1057         case 0x01:
1058             errStr += " DIMM info not valid";
1059             break;
1060         case 0x02:
1061             errStr += " CHN info not valid";
1062             break;
1063         case 0x03:
1064             errStr += " CPU info not valid";
1065             break;
1066         default:
1067             errStr += " Unknown";
1068     }
1069 
1070     if (((data->eventType & 0x80) >> 7) == 0)
1071     {
1072         errStr += " Assertion";
1073     }
1074     else
1075     {
1076         errStr += " Deassertion";
1077     }
1078 
1079     return;
1080 }
1081 
1082 static void parseOemSel(TsOemSELEntry* data, std::string& errStr)
1083 {
1084     std::stringstream tmpStream;
1085     tmpStream << std::hex << std::uppercase << std::setfill('0');
1086 
1087     switch (data->recordType)
1088     {
1089         case 0xC0:
1090             tmpStream << "VID:0x" << std::setw(2) << (int)data->oemData[1]
1091                       << std::setw(2) << (int)data->oemData[0] << " DID:0x"
1092                       << std::setw(2) << (int)data->oemData[3] << std::setw(2)
1093                       << (int)data->oemData[2] << " Slot:0x" << std::setw(2)
1094                       << (int)data->oemData[4] << " Error ID:0x" << std::setw(2)
1095                       << (int)data->oemData[5];
1096             break;
1097         case 0xC2:
1098             tmpStream << "Extra info:0x" << std::setw(2)
1099                       << (int)data->oemData[1] << " MSCOD:0x" << std::setw(2)
1100                       << (int)data->oemData[3] << std::setw(2)
1101                       << (int)data->oemData[2] << " MCACOD:0x" << std::setw(2)
1102                       << (int)data->oemData[5] << std::setw(2)
1103                       << (int)data->oemData[4];
1104             break;
1105         case 0xC3:
1106             int bank = (data->oemData[1] & 0xf0) >> 4;
1107             int col = ((data->oemData[1] & 0x0f) << 8) | data->oemData[2];
1108 
1109             tmpStream << "Fail Device:0x" << std::setw(2)
1110                       << (int)data->oemData[0] << " Bank:0x" << std::setw(2)
1111                       << bank << " Column:0x" << std::setw(2) << col
1112                       << " Failed Row:0x" << std::setw(2)
1113                       << (int)data->oemData[3] << std::setw(2)
1114                       << (int)data->oemData[4] << std::setw(2)
1115                       << (int)data->oemData[5];
1116     }
1117 
1118     errStr = tmpStream.str();
1119 
1120     return;
1121 }
1122 
1123 static std::string dimmLocationStr(uint8_t socket, uint8_t channel,
1124                                    uint8_t slot)
1125 {
1126     uint8_t sled = (socket >> 4) & 0x3;
1127 
1128     socket &= 0xf;
1129     if (channel == 0xFF && slot == 0xFF)
1130     {
1131         return std::format(
1132             "DIMM Slot Location: Sled {:02}/Socket {:02}, Channel unknown"
1133             ", Slot unknown, DIMM unknown",
1134             sled, socket);
1135     }
1136     else
1137     {
1138         channel &= 0xf;
1139         slot &= 0xf;
1140         const char label[] = {'A', 'C', 'B', 'D'};
1141         uint8_t idx = socket * 2 + slot;
1142         return std::format("DIMM Slot Location: Sled {:02}/Socket {:02}"
1143                            ", Channel {:02}, Slot {:02} DIMM {}",
1144                            sled, socket, channel, slot,
1145                            (idx < sizeof(label))
1146                                ? label[idx] + std::to_string(channel)
1147                                : "NA");
1148     }
1149 }
1150 
1151 static void parseOemUnifiedSel(NtsOemSELEntry* data, std::string& errStr)
1152 {
1153     uint8_t* ptr = data->oemData;
1154     uint8_t eventType = ptr[5] & 0xf;
1155     int genInfo = ptr[0];
1156     int errType = genInfo & 0x0f;
1157     std::vector<std::string> dimmErr = {
1158         "Memory training failure",
1159         "Memory correctable error",
1160         "Memory uncorrectable error",
1161         "Memory correctable error (Patrol scrub)",
1162         "Memory uncorrectable error (Patrol scrub)",
1163         "Memory Parity Error (PCC=0)",
1164         "Memory Parity Error (PCC=1)",
1165         "Memory PMIC Error",
1166         "CXL Memory training error",
1167         "Reserved"};
1168     std::vector<std::string> postEvent = {
1169         "System PXE boot fail",
1170         "CMOS/NVRAM configuration cleared",
1171         "TPM Self-Test Fail",
1172         "Boot Drive failure",
1173         "Data Drive failure",
1174         "Received invalid boot order request from BMC",
1175         "System HTTP boot fail",
1176         "BIOS fails to get the certificate from BMC",
1177         "Password cleared by jumper",
1178         "DXE FV check failure",
1179         "AMD ABL failure",
1180         "Reserved"};
1181     std::vector<std::string> certErr = {
1182         "No certificate at BMC", "IPMI transaction fail",
1183         "Certificate data corrupted", "Reserved"};
1184     std::vector<std::string> pcieEvent = {
1185         "PCIe DPC Event",
1186         "PCIe LER Event",
1187         "PCIe Link Retraining and Recovery",
1188         "PCIe Link CRC Error Check and Retry",
1189         "PCIe Corrupt Data Containment",
1190         "PCIe Express ECRC",
1191         "Reserved"};
1192     std::vector<std::string> memEvent = {
1193         "Memory PPR event",
1194         "Memory Correctable Error logging limit reached",
1195         "Memory disable/map-out for FRB",
1196         "Memory SDDC",
1197         "Memory Address range/Partial mirroring",
1198         "Memory ADDDC",
1199         "Memory SMBus hang recovery",
1200         "No DIMM in System",
1201         "Reserved"};
1202     std::vector<std::string> memPprTime = {"Boot time", "Autonomous",
1203                                            "Run time", "Reserved"};
1204     std::vector<std::string> memPpr = {"PPR success", "PPR fail", "PPR request",
1205                                        "Reserved"};
1206     std::vector<std::string> memAdddc = {
1207         "Bank VLS", "r-Bank VLS + re-buddy", "r-Bank VLS + Rank VLS",
1208         "r-Rank VLS + re-buddy", "Reserved"};
1209     std::vector<std::string> pprEvent = {"PPR disable", "Soft PPR", "Hard PPR",
1210                                          "Reserved"};
1211 
1212     std::stringstream tmpStream;
1213 
1214     switch (errType)
1215     {
1216         case unifiedPcieErr:
1217             tmpStream << std::format(
1218                 "GeneralInfo: x86/PCIeErr(0x{:02X})"
1219                 ", Bus {:02X}/Dev {:02X}/Fun {:02X}, TotalErrID1Cnt: 0x{:04X}"
1220                 ", ErrID2: 0x{:02X}, ErrID1: 0x{:02X}",
1221                 genInfo, ptr[8], ptr[7] >> 3, ptr[7] & 0x7,
1222                 (ptr[10] << 8) | ptr[9], ptr[11], ptr[12]);
1223             break;
1224         case unifiedMemErr:
1225             eventType = ptr[9] & 0xf;
1226             tmpStream << std::format(
1227                 "GeneralInfo: MemErr(0x{:02X}), {}, DIMM Failure Event: {}",
1228                 genInfo, dimmLocationStr(ptr[5], ptr[6], ptr[7]),
1229                 dimmErr[std::min(eventType,
1230                                  static_cast<uint8_t>(dimmErr.size() - 1))]);
1231 
1232             if (static_cast<MemErrType>(eventType) == MemErrType::memTrainErr ||
1233                 static_cast<MemErrType>(eventType) == MemErrType::memPmicErr)
1234             {
1235                 bool amd = ptr[9] & 0x80;
1236                 tmpStream << std::format(
1237                     ", Major Code: 0x{:02X}, Minor Code: 0x{:0{}X}", ptr[10],
1238                     amd ? (ptr[12] << 8 | ptr[11]) : ptr[11], amd ? 4 : 2);
1239             }
1240             break;
1241         case unifiedIioErr:
1242             tmpStream << std::format(
1243                 "GeneralInfo: IIOErr(0x{:02X})"
1244                 ", IIO Port Location: Sled {:02}/Socket {:02}, Stack 0x{:02X}"
1245                 ", Error Type: 0x{:02X}, Error Severity: 0x{:02X}"
1246                 ", Error ID: 0x{:02X}",
1247                 genInfo, (ptr[5] >> 4) & 0x3, ptr[5] & 0xf, ptr[6], ptr[10],
1248                 ptr[11] & 0xf, ptr[12]);
1249             break;
1250         case unifiedPostEvt:
1251             tmpStream << std::format(
1252                 "GeneralInfo: POST(0x{:02X}), POST Failure Event: {}", genInfo,
1253                 postEvent[std::min(
1254                     eventType, static_cast<uint8_t>(postEvent.size() - 1))]);
1255 
1256             switch (static_cast<PostEvtType>(eventType))
1257             {
1258                 case PostEvtType::pxeBootFail:
1259                 case PostEvtType::httpBootFail:
1260                 {
1261                     uint8_t failType = ptr[10] & 0xf;
1262                     tmpStream
1263                         << std::format(", Fail Type: {}, Error Code: 0x{:02X}",
1264                                        (failType == 4 || failType == 6)
1265                                            ? std::format("IPv{} fail", failType)
1266                                            : std::format("0x{:02X}", ptr[10]),
1267                                        ptr[11]);
1268                     break;
1269                 }
1270                 case PostEvtType::getCertFail:
1271                     tmpStream << std::format(
1272                         ", Failure Detail: {}",
1273                         certErr[std::min(
1274                             ptr[9], static_cast<uint8_t>(certErr.size() - 1))]);
1275                     break;
1276                 case PostEvtType::amdAblFail:
1277                     tmpStream << std::format(", ABL Error Code: 0x{:04X}",
1278                                              (ptr[12] << 8) | ptr[11]);
1279                     break;
1280             }
1281             break;
1282         case unifiedPcieEvt:
1283             tmpStream << std::format(
1284                 "GeneralInfo: PCIeEvent(0x{:02X}), PCIe Failure Event: {}",
1285                 genInfo,
1286                 pcieEvent[std::min(
1287                     eventType, static_cast<uint8_t>(pcieEvent.size() - 1))]);
1288 
1289             if (static_cast<PcieEvtType>(eventType) == PcieEvtType::dpc)
1290             {
1291                 tmpStream << std::format(
1292                     ", Status: 0x{:04X}, Source ID: 0x{:04X}",
1293                     (ptr[8] << 8) | ptr[7], (ptr[10] << 8) | ptr[9]);
1294             }
1295             break;
1296         case unifiedMemEvt:
1297             eventType = ptr[9] & 0xf;
1298             tmpStream
1299                 << std::format("GeneralInfo: MemEvent(0x{:02X})", genInfo)
1300                 << (static_cast<MemEvtType>(eventType) != MemEvtType::noDimm
1301                         ? std::format(", {}",
1302                                       dimmLocationStr(ptr[5], ptr[6], ptr[7]))
1303                         : "")
1304                 << ", DIMM Failure Event: ";
1305 
1306             switch (static_cast<MemEvtType>(eventType))
1307             {
1308                 case MemEvtType::ppr:
1309                     tmpStream << std::format("{} {}",
1310                                              memPprTime[(ptr[10] >> 2) & 0x3],
1311                                              memPpr[ptr[10] & 0x3]);
1312                     break;
1313                 case MemEvtType::adddc:
1314                     tmpStream << std::format(
1315                         "{} {}",
1316                         memEvent[std::min(eventType, static_cast<uint8_t>(
1317                                                          memEvent.size() - 1))],
1318                         memAdddc[std::min(
1319                             static_cast<uint8_t>(ptr[11] & 0xf),
1320                             static_cast<uint8_t>(memAdddc.size() - 1))]);
1321                     break;
1322                 default:
1323                     tmpStream << std::format(
1324                         "{}", memEvent[std::min(
1325                                   eventType,
1326                                   static_cast<uint8_t>(memEvent.size() - 1))]);
1327                     break;
1328             }
1329             break;
1330         case unifiedBootGuard:
1331             tmpStream << std::format(
1332                 "GeneralInfo: Boot Guard ACM Failure Events(0x{:02X})"
1333                 ", Error Class: 0x{:02X}, Error Code: 0x{:02X}",
1334                 genInfo, ptr[9], ptr[10]);
1335             break;
1336         case unifiedPprEvt:
1337             tmpStream << std::format(
1338                 "GeneralInfo: PPREvent(0x{:02X}), {}"
1339                 ", DIMM Info: {:02X}{:02X}{:02X}{:02X}{:02X}{:02X}{:02X}",
1340                 genInfo,
1341                 pprEvent[std::min(eventType,
1342                                   static_cast<uint8_t>(pprEvent.size() - 1))],
1343                 ptr[6], ptr[7], ptr[8], ptr[9], ptr[10], ptr[11], ptr[12]);
1344             break;
1345         default:
1346             std::vector<uint8_t> oemData(ptr, ptr + 13);
1347             std::string oemDataStr;
1348             toHexStr(oemData, oemDataStr);
1349             tmpStream << std::format("Undefined Error Type(0x{:02X}), Raw: {}",
1350                                      errType, oemDataStr);
1351     }
1352 
1353     errStr = tmpStream.str();
1354 
1355     return;
1356 }
1357 
1358 static void parseSelData(uint8_t fruId, std::vector<uint8_t>& reqData,
1359                          std::string& msgLog)
1360 {
1361     /* Get record type */
1362     int recType = reqData[2];
1363     std::string errType, errLog;
1364 
1365     uint8_t* ptr = NULL;
1366 
1367     std::stringstream recTypeStream;
1368     recTypeStream << std::hex << std::uppercase << std::setfill('0')
1369                   << std::setw(2) << recType;
1370 
1371     msgLog = "SEL Entry: FRU: " + std::to_string(fruId) + ", Record: ";
1372 
1373     if (recType == stdErrType)
1374     {
1375         StdSELEntry* data = reinterpret_cast<StdSELEntry*>(&reqData[0]);
1376         std::string sensorName;
1377 
1378         errType = stdErr;
1379         if (data->sensorType == 0x1F)
1380         {
1381             sensorName = "OS";
1382         }
1383         else
1384         {
1385             auto findSensorName = sensorNameTable.find(data->sensorNum);
1386             if (findSensorName == sensorNameTable.end())
1387             {
1388                 sensorName = "Unknown";
1389             }
1390             else
1391             {
1392                 sensorName = findSensorName->second.first;
1393             }
1394         }
1395 
1396         time_t timeStamp = static_cast<time_t>(data->timeStamp);
1397         std::string timeStr;
1398         std::tm ts;
1399         if (localtime_r(&timeStamp, &ts))
1400         {
1401             char buf[64];
1402             if (strftime(buf, sizeof(buf), "%c", &ts))
1403             {
1404                 timeStr = buf;
1405             }
1406         }
1407 
1408         parseStdSel(data, errLog);
1409         ptr = &(data->eventData1);
1410         std::vector<uint8_t> evtData(ptr, ptr + 3);
1411         std::string eventData;
1412         toHexStr(evtData, eventData);
1413 
1414         std::stringstream senNumStream;
1415         senNumStream << std::hex << std::uppercase << std::setfill('0')
1416                      << std::setw(2) << (int)(data->sensorNum);
1417 
1418         msgLog += errType + " (0x" + recTypeStream.str() +
1419                   "), Time: " + timeStr + ", Sensor: " + sensorName + " (0x" +
1420                   senNumStream.str() + "), Event Data: (" + eventData + ") " +
1421                   errLog;
1422     }
1423     else if ((recType >= oemTSErrTypeMin) && (recType <= oemTSErrTypeMax))
1424     {
1425         /* timestamped OEM SEL records */
1426         TsOemSELEntry* data = reinterpret_cast<TsOemSELEntry*>(&reqData[0]);
1427         ptr = data->mfrId;
1428         std::vector<uint8_t> mfrIdData(ptr, ptr + 3);
1429         std::string mfrIdStr;
1430         toHexStr(mfrIdData, mfrIdStr);
1431 
1432         ptr = data->oemData;
1433         std::vector<uint8_t> oemData(ptr, ptr + 6);
1434         std::string oemDataStr;
1435         toHexStr(oemData, oemDataStr);
1436 
1437         time_t timeStamp = static_cast<time_t>(data->timeStamp);
1438         std::string timeStr;
1439         std::tm ts;
1440         if (localtime_r(&timeStamp, &ts))
1441         {
1442             char buf[64];
1443             if (strftime(buf, sizeof(buf), "%c", &ts))
1444             {
1445                 timeStr = buf;
1446             }
1447         }
1448 
1449         errType = oemTSErr;
1450         parseOemSel(data, errLog);
1451 
1452         msgLog += errType + " (0x" + recTypeStream.str() +
1453                   "), Time: " + timeStr + ", MFG ID: " + mfrIdStr +
1454                   ", OEM Data: (" + oemDataStr + ") " + errLog;
1455     }
1456     else if (recType == fbUniErrType)
1457     {
1458         NtsOemSELEntry* data = reinterpret_cast<NtsOemSELEntry*>(&reqData[0]);
1459         errType = fbUniSELErr;
1460         parseOemUnifiedSel(data, errLog);
1461         msgLog += errType + " (0x" + recTypeStream.str() + "), " + errLog;
1462     }
1463     else if ((recType >= oemNTSErrTypeMin) && (recType <= oemNTSErrTypeMax))
1464     {
1465         /* Non timestamped OEM SEL records */
1466         NtsOemSELEntry* data = reinterpret_cast<NtsOemSELEntry*>(&reqData[0]);
1467         errType = oemNTSErr;
1468 
1469         ptr = data->oemData;
1470         std::vector<uint8_t> oemData(ptr, ptr + 13);
1471         std::string oemDataStr;
1472         toHexStr(oemData, oemDataStr);
1473 
1474         parseOemSel((TsOemSELEntry*)data, errLog);
1475         msgLog += errType + " (0x" + recTypeStream.str() + "), OEM Data: (" +
1476                   oemDataStr + ") " + errLog;
1477     }
1478     else
1479     {
1480         errType = unknownErr;
1481         toHexStr(reqData, errLog);
1482         msgLog += errType + " (0x" + recTypeStream.str() +
1483                   ") RawData: " + errLog;
1484     }
1485 }
1486 
1487 } // namespace fb_oem::ipmi::sel
1488 
1489 namespace ipmi
1490 {
1491 
1492 namespace storage
1493 {
1494 
1495 static void registerSELFunctions() __attribute__((constructor));
1496 static fb_oem::ipmi::sel::SELData selObj __attribute__((init_priority(101)));
1497 
1498 ipmi::RspType<uint8_t,  // SEL version
1499               uint16_t, // SEL entry count
1500               uint16_t, // free space
1501               uint32_t, // last add timestamp
1502               uint32_t, // last erase timestamp
1503               uint8_t>  // operation support
1504     ipmiStorageGetSELInfo()
1505 {
1506     fb_oem::ipmi::sel::GetSELInfoData info;
1507 
1508     selObj.getInfo(info);
1509     return ipmi::responseSuccess(info.selVersion, info.entries, info.freeSpace,
1510                                  info.addTimeStamp, info.eraseTimeStamp,
1511                                  info.operationSupport);
1512 }
1513 
1514 ipmi::RspType<uint16_t, std::vector<uint8_t>>
1515     ipmiStorageGetSELEntry(std::vector<uint8_t> data)
1516 {
1517     if (data.size() != sizeof(fb_oem::ipmi::sel::GetSELEntryRequest))
1518     {
1519         return ipmi::responseReqDataLenInvalid();
1520     }
1521 
1522     fb_oem::ipmi::sel::GetSELEntryRequest* reqData =
1523         reinterpret_cast<fb_oem::ipmi::sel::GetSELEntryRequest*>(&data[0]);
1524 
1525     if (reqData->reservID != 0)
1526     {
1527         if (!checkSELReservation(reqData->reservID))
1528         {
1529             return ipmi::responseInvalidReservationId();
1530         }
1531     }
1532 
1533     uint16_t selCnt = selObj.getCount();
1534     if (selCnt == 0)
1535     {
1536         return ipmi::responseSensorInvalid();
1537     }
1538 
1539     /* If it is asked for first entry */
1540     if (reqData->recordID == fb_oem::ipmi::sel::firstEntry)
1541     {
1542         /* First Entry (0x0000) as per Spec */
1543         reqData->recordID = 1;
1544     }
1545     else if (reqData->recordID == fb_oem::ipmi::sel::lastEntry)
1546     {
1547         /* Last entry (0xFFFF) as per Spec */
1548         reqData->recordID = selCnt;
1549     }
1550 
1551     std::string ipmiRaw;
1552 
1553     if (selObj.getEntry(reqData->recordID, ipmiRaw) < 0)
1554     {
1555         return ipmi::responseSensorInvalid();
1556     }
1557 
1558     std::vector<uint8_t> recDataBytes;
1559     if (fromHexStr(ipmiRaw, recDataBytes) < 0)
1560     {
1561         return ipmi::responseUnspecifiedError();
1562     }
1563 
1564     /* Identify the next SEL record ID. If recordID is same as
1565      * total SeL count then next id should be last entry else
1566      * it should be incremented by 1 to current RecordID
1567      */
1568     uint16_t nextRecord;
1569     if (reqData->recordID == selCnt)
1570     {
1571         nextRecord = fb_oem::ipmi::sel::lastEntry;
1572     }
1573     else
1574     {
1575         nextRecord = reqData->recordID + 1;
1576     }
1577 
1578     if (reqData->readLen == fb_oem::ipmi::sel::entireRecord)
1579     {
1580         return ipmi::responseSuccess(nextRecord, recDataBytes);
1581     }
1582     else
1583     {
1584         if (reqData->offset >= fb_oem::ipmi::sel::selRecordSize ||
1585             reqData->readLen > fb_oem::ipmi::sel::selRecordSize)
1586         {
1587             return ipmi::responseUnspecifiedError();
1588         }
1589         std::vector<uint8_t> recPartData;
1590 
1591         auto diff = fb_oem::ipmi::sel::selRecordSize - reqData->offset;
1592         auto readLength = std::min(diff, static_cast<int>(reqData->readLen));
1593 
1594         for (int i = 0; i < readLength; i++)
1595         {
1596             recPartData.push_back(recDataBytes[i + reqData->offset]);
1597         }
1598         return ipmi::responseSuccess(nextRecord, recPartData);
1599     }
1600 }
1601 
1602 ipmi::RspType<uint16_t>
1603     ipmiStorageAddSELEntry(ipmi::Context::ptr ctx, std::vector<uint8_t> data)
1604 {
1605     /* Per the IPMI spec, need to cancel any reservation when a
1606      * SEL entry is added
1607      */
1608     cancelSELReservation();
1609 
1610     if (data.size() != fb_oem::ipmi::sel::selRecordSize)
1611     {
1612         return ipmi::responseReqDataLenInvalid();
1613     }
1614 
1615     std::string ipmiRaw, logErr;
1616     toHexStr(data, ipmiRaw);
1617 
1618     /* Parse sel data and get an error log to be filed */
1619     fb_oem::ipmi::sel::parseSelData((ctx->hostIdx + 1), data, logErr);
1620 
1621     static const std::string openBMCMessageRegistryVersion("0.1");
1622     std::string messageID =
1623         "OpenBMC." + openBMCMessageRegistryVersion + ".SELEntryAdded";
1624 
1625     /* Log the Raw SEL message to the journal */
1626     std::string journalMsg = "SEL Entry Added: " + ipmiRaw;
1627 
1628     phosphor::logging::log<phosphor::logging::level::INFO>(
1629         journalMsg.c_str(),
1630         phosphor::logging::entry("IPMISEL_MESSAGE_ID=%s", messageID.c_str()),
1631         phosphor::logging::entry("IPMISEL_MESSAGE_ARGS=%s", logErr.c_str()));
1632 
1633     std::map<std::string, std::string> ad;
1634     std::string severity = "xyz.openbmc_project.Logging.Entry.Level.Critical";
1635     ad.emplace("IPMI_RAW", ipmiRaw);
1636 
1637     auto bus = sdbusplus::bus::new_default();
1638     auto reqMsg = bus.new_method_call(
1639         "xyz.openbmc_project.Logging", "/xyz/openbmc_project/logging",
1640         "xyz.openbmc_project.Logging.Create", "Create");
1641     reqMsg.append(logErr, severity, ad);
1642 
1643     try
1644     {
1645         bus.call(reqMsg);
1646     }
1647     catch (sdbusplus::exception_t& e)
1648     {
1649         phosphor::logging::log<phosphor::logging::level::ERR>(e.what());
1650     }
1651 
1652     int responseID = selObj.addEntry(ipmiRaw.c_str());
1653     if (responseID < 0)
1654     {
1655         return ipmi::responseUnspecifiedError();
1656     }
1657     return ipmi::responseSuccess((uint16_t)responseID);
1658 }
1659 
1660 ipmi::RspType<uint8_t> ipmiStorageClearSEL(uint16_t reservationID,
1661                                            const std::array<uint8_t, 3>& clr,
1662                                            uint8_t eraseOperation)
1663 {
1664     if (!checkSELReservation(reservationID))
1665     {
1666         return ipmi::responseInvalidReservationId();
1667     }
1668 
1669     static constexpr std::array<uint8_t, 3> clrExpected = {'C', 'L', 'R'};
1670     if (clr != clrExpected)
1671     {
1672         return ipmi::responseInvalidFieldRequest();
1673     }
1674 
1675     /* If there is no sel then return erase complete */
1676     if (selObj.getCount() == 0)
1677     {
1678         return ipmi::responseSuccess(fb_oem::ipmi::sel::eraseComplete);
1679     }
1680 
1681     /* Erasure status cannot be fetched, so always return erasure
1682      * status as `erase completed`.
1683      */
1684     if (eraseOperation == fb_oem::ipmi::sel::getEraseStatus)
1685     {
1686         return ipmi::responseSuccess(fb_oem::ipmi::sel::eraseComplete);
1687     }
1688 
1689     /* Check that initiate erase is correct */
1690     if (eraseOperation != fb_oem::ipmi::sel::initiateErase)
1691     {
1692         return ipmi::responseInvalidFieldRequest();
1693     }
1694 
1695     /* Per the IPMI spec, need to cancel any reservation when the
1696      * SEL is cleared
1697      */
1698     cancelSELReservation();
1699 
1700     /* Clear the complete Sel Json object */
1701     if (selObj.clear() < 0)
1702     {
1703         return ipmi::responseUnspecifiedError();
1704     }
1705 
1706     return ipmi::responseSuccess(fb_oem::ipmi::sel::eraseComplete);
1707 }
1708 
1709 ipmi::RspType<uint32_t> ipmiStorageGetSELTime()
1710 {
1711     struct timespec selTime = {};
1712 
1713     if (clock_gettime(CLOCK_REALTIME, &selTime) < 0)
1714     {
1715         return ipmi::responseUnspecifiedError();
1716     }
1717 
1718     return ipmi::responseSuccess(selTime.tv_sec);
1719 }
1720 
1721 ipmi::RspType<> ipmiStorageSetSELTime(uint32_t)
1722 {
1723     // Set SEL Time is not supported
1724     return ipmi::responseInvalidCommand();
1725 }
1726 
1727 ipmi::RspType<uint16_t> ipmiStorageGetSELTimeUtcOffset()
1728 {
1729     /* TODO: For now, the SEL time stamp is based on UTC time,
1730      * so return 0x0000 as offset. Might need to change once
1731      * supporting zones in SEL time stamps
1732      */
1733 
1734     uint16_t utcOffset = 0x0000;
1735     return ipmi::responseSuccess(utcOffset);
1736 }
1737 
1738 void registerSELFunctions()
1739 {
1740     // <Get SEL Info>
1741     ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
1742                           ipmi::storage::cmdGetSelInfo, ipmi::Privilege::User,
1743                           ipmiStorageGetSELInfo);
1744 
1745     // <Get SEL Entry>
1746     ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
1747                           ipmi::storage::cmdGetSelEntry, ipmi::Privilege::User,
1748                           ipmiStorageGetSELEntry);
1749 
1750     // <Add SEL Entry>
1751     ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
1752                           ipmi::storage::cmdAddSelEntry,
1753                           ipmi::Privilege::Operator, ipmiStorageAddSELEntry);
1754 
1755     // <Clear SEL>
1756     ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
1757                           ipmi::storage::cmdClearSel, ipmi::Privilege::Operator,
1758                           ipmiStorageClearSEL);
1759 
1760     // <Get SEL Time>
1761     ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
1762                           ipmi::storage::cmdGetSelTime, ipmi::Privilege::User,
1763                           ipmiStorageGetSELTime);
1764 
1765     // <Set SEL Time>
1766     ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
1767                           ipmi::storage::cmdSetSelTime,
1768                           ipmi::Privilege::Operator, ipmiStorageSetSELTime);
1769 
1770     // <Get SEL Time UTC Offset>
1771     ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
1772                           ipmi::storage::cmdGetSelTimeUtcOffset,
1773                           ipmi::Privilege::User,
1774                           ipmiStorageGetSELTimeUtcOffset);
1775 
1776     return;
1777 }
1778 
1779 } // namespace storage
1780 } // namespace ipmi
1781