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