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