1 #include <algorithm>
2 #include <arpa/inet.h>
3 #include <chrono>
4 #include <cstdio>
5 #include <experimental/filesystem>
6 #include <mapper.h>
7 #include <string>
8 #include <systemd/sd-bus.h>
9 
10 #include <phosphor-logging/log.hpp>
11 #include <phosphor-logging/elog-errors.hpp>
12 #include <sdbusplus/server.hpp>
13 
14 #include "host-ipmid/ipmid-api.h"
15 #include "read_fru_data.hpp"
16 #include "selutility.hpp"
17 #include "storageaddsel.h"
18 #include "storagehandler.h"
19 #include "utils.hpp"
20 #include "xyz/openbmc_project/Common/error.hpp"
21 
22 
23 void register_netfn_storage_functions() __attribute__((constructor));
24 
25 unsigned int   g_sel_time    = 0xFFFFFFFF;
26 extern unsigned short g_sel_reserve;
27 
28 namespace {
29 constexpr auto TIME_INTERFACE = "xyz.openbmc_project.Time.EpochTime";
30 constexpr auto HOST_TIME_PATH = "/xyz/openbmc_project/time/host";
31 constexpr auto DBUS_PROPERTIES = "org.freedesktop.DBus.Properties";
32 constexpr auto PROPERTY_ELAPSED= "Elapsed";
33 
34 const char* getTimeString(const uint64_t& usecSinceEpoch)
35 {
36     using namespace std::chrono;
37     system_clock::time_point tp{microseconds(usecSinceEpoch)};
38     auto t = system_clock::to_time_t(tp);
39     return std::ctime(&t);
40 }
41 }
42 
43 namespace cache
44 {
45     /*
46      * This cache contains the object paths of the logging entries sorted in the
47      * order of the filename(numeric order). The cache is initialized by
48      * invoking readLoggingObjectPaths with the cache as the parameter. The
49      * cache is invoked in the execution of the Get SEL info and Delete SEL
50      * entry command. The Get SEL Info command is typically invoked before the
51      * Get SEL entry command, so the cache is utilized for responding to Get SEL
52      * entry command. The cache is invalidated by clearing after Delete SEL
53      * entry and Clear SEL command.
54      */
55     ipmi::sel::ObjectPaths paths;
56 
57 } // namespace objectPathsCache
58 
59 using InternalFailure =
60         sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
61 using namespace phosphor::logging;
62 using namespace ipmi::fru;
63 
64 /**
65  * @enum Device access mode
66  */
67 enum class AccessMode
68 {
69     bytes, ///< Device is accessed by bytes
70     words  ///< Device is accessed by words
71 };
72 
73 
74 ipmi_ret_t ipmi_storage_wildcard(ipmi_netfn_t netfn, ipmi_cmd_t cmd,
75                               ipmi_request_t request, ipmi_response_t response,
76                               ipmi_data_len_t data_len, ipmi_context_t context)
77 {
78     printf("Handling STORAGE WILDCARD Netfn:[0x%X], Cmd:[0x%X]\n",netfn, cmd);
79     // Status code.
80     ipmi_ret_t rc = IPMI_CC_INVALID;
81     *data_len = 0;
82     return rc;
83 }
84 
85 ipmi_ret_t getSELInfo(ipmi_netfn_t netfn, ipmi_cmd_t cmd,
86                       ipmi_request_t request, ipmi_response_t response,
87                       ipmi_data_len_t data_len, ipmi_context_t context)
88 {
89     std::vector<uint8_t> outPayload(sizeof(ipmi::sel::GetSELInfoResponse));
90     auto responseData = reinterpret_cast<ipmi::sel::GetSELInfoResponse*>
91             (outPayload.data());
92 
93     responseData->selVersion = ipmi::sel::selVersion;
94     // Last erase timestamp is not available from log manager.
95     responseData->eraseTimeStamp = ipmi::sel::invalidTimeStamp;
96     responseData->operationSupport = ipmi::sel::operationSupport;
97 
98     ipmi::sel::readLoggingObjectPaths(cache::paths);
99     responseData->entries = 0;
100     responseData->addTimeStamp = ipmi::sel::invalidTimeStamp;
101 
102     if (!cache::paths.empty())
103     {
104         responseData->entries = static_cast<uint16_t>(cache::paths.size());
105 
106         try
107         {
108             responseData->addTimeStamp = static_cast<uint32_t>(
109                     (ipmi::sel::getEntryTimeStamp(cache::paths.back())
110                     .count()));
111         }
112         catch (InternalFailure& e)
113         {
114         }
115         catch (const std::runtime_error& e)
116         {
117             log<level::ERR>(e.what());
118         }
119     }
120 
121     memcpy(response, outPayload.data(), outPayload.size());
122     *data_len = outPayload.size();
123 
124     return IPMI_CC_OK;
125 }
126 
127 ipmi_ret_t getSELEntry(ipmi_netfn_t netfn, ipmi_cmd_t cmd,
128                        ipmi_request_t request, ipmi_response_t response,
129                        ipmi_data_len_t data_len, ipmi_context_t context)
130 {
131     auto requestData = reinterpret_cast<const ipmi::sel::GetSELEntryRequest*>
132                    (request);
133 
134     if (requestData->reservationID != 0)
135     {
136         if (g_sel_reserve != requestData->reservationID)
137         {
138             *data_len = 0;
139             return IPMI_CC_INVALID_RESERVATION_ID;
140         }
141     }
142 
143     if (cache::paths.empty())
144     {
145         *data_len = 0;
146         return IPMI_CC_SENSOR_INVALID;
147     }
148 
149     ipmi::sel::ObjectPaths::const_iterator iter;
150 
151     // Check for the requested SEL Entry.
152     if (requestData->selRecordID == ipmi::sel::firstEntry)
153     {
154         iter = cache::paths.begin();
155     }
156     else if (requestData->selRecordID == ipmi::sel::lastEntry)
157     {
158         iter = cache::paths.end();
159     }
160     else
161     {
162         std::string objPath = std::string(ipmi::sel::logBasePath) + "/" +
163                               std::to_string(requestData->selRecordID);
164 
165         iter = std::find(cache::paths.begin(), cache::paths.end(), objPath);
166         if (iter == cache::paths.end())
167         {
168             *data_len = 0;
169             return IPMI_CC_SENSOR_INVALID;
170         }
171     }
172 
173     ipmi::sel::GetSELEntryResponse record {};
174 
175     // Convert the log entry into SEL record.
176     try
177     {
178         record = ipmi::sel::convertLogEntrytoSEL(*iter);
179     }
180     catch (InternalFailure& e)
181     {
182         *data_len = 0;
183         return IPMI_CC_UNSPECIFIED_ERROR;
184     }
185     catch (const std::runtime_error& e)
186     {
187         log<level::ERR>(e.what());
188         *data_len = 0;
189         return IPMI_CC_UNSPECIFIED_ERROR;
190     }
191 
192 
193     // Identify the next SEL record ID
194     if(iter != cache::paths.end())
195     {
196         ++iter;
197         if (iter == cache::paths.end())
198         {
199             record.nextRecordID = ipmi::sel::lastEntry;
200         }
201         else
202         {
203             namespace fs = std::experimental::filesystem;
204             fs::path path(*iter);
205             record.nextRecordID = static_cast<uint16_t>
206                      (std::stoul(std::string(path.filename().c_str())));
207         }
208     }
209     else
210     {
211         record.nextRecordID = ipmi::sel::lastEntry;
212     }
213 
214     if (requestData->readLength == ipmi::sel::entireRecord)
215     {
216         memcpy(response, &record, sizeof(record));
217         *data_len = sizeof(record);
218     }
219     else
220     {
221         if (requestData->offset >= ipmi::sel::selRecordSize ||
222             requestData->readLength > ipmi::sel::selRecordSize)
223         {
224             *data_len = 0;
225             return IPMI_CC_INVALID_FIELD_REQUEST;
226         }
227 
228         auto diff = ipmi::sel::selRecordSize - requestData->offset;
229         auto readLength = std::min(diff,
230                                    static_cast<int>(requestData->readLength));
231 
232         memcpy(response, &record.nextRecordID, sizeof(record.nextRecordID));
233         memcpy(static_cast<uint8_t*>(response) + sizeof(record.nextRecordID),
234                &record.recordID + requestData->offset, readLength);
235         *data_len = sizeof(record.nextRecordID) + readLength;
236     }
237 
238     return IPMI_CC_OK;
239 }
240 
241 ipmi_ret_t deleteSELEntry(ipmi_netfn_t netfn, ipmi_cmd_t cmd,
242                           ipmi_request_t request, ipmi_response_t response,
243                           ipmi_data_len_t data_len, ipmi_context_t context)
244 {
245     namespace fs = std::experimental::filesystem;
246     auto requestData = reinterpret_cast<const ipmi::sel::DeleteSELEntryRequest*>
247             (request);
248 
249     if (g_sel_reserve != requestData->reservationID)
250     {
251         *data_len = 0;
252         return IPMI_CC_INVALID_RESERVATION_ID;
253     }
254 
255     ipmi::sel::readLoggingObjectPaths(cache::paths);
256 
257     if (cache::paths.empty())
258     {
259         *data_len = 0;
260         return IPMI_CC_SENSOR_INVALID;
261     }
262 
263     ipmi::sel::ObjectPaths::const_iterator iter;
264     uint16_t delRecordID = 0;
265 
266     if (requestData->selRecordID == ipmi::sel::firstEntry)
267     {
268         iter = cache::paths.begin();
269         fs::path path(*iter);
270         delRecordID = static_cast<uint16_t>
271                 (std::stoul(std::string(path.filename().c_str())));
272     }
273     else if (requestData->selRecordID == ipmi::sel::lastEntry)
274     {
275         iter = cache::paths.end();
276         fs::path path(*iter);
277         delRecordID = static_cast<uint16_t>
278                 (std::stoul(std::string(path.filename().c_str())));
279     }
280     else
281     {
282         std::string objPath = std::string(ipmi::sel::logBasePath) + "/" +
283                               std::to_string(requestData->selRecordID);
284 
285         iter = std::find(cache::paths.begin(), cache::paths.end(), objPath);
286         if (iter == cache::paths.end())
287         {
288             *data_len = 0;
289             return IPMI_CC_SENSOR_INVALID;
290         }
291         delRecordID = requestData->selRecordID;
292     }
293 
294     sdbusplus::bus::bus bus{ipmid_get_sd_bus_connection()};
295     std::string service;
296 
297     try
298     {
299         service = ipmi::getService(bus, ipmi::sel::logDeleteIntf, *iter);
300     }
301     catch (const std::runtime_error& e)
302     {
303         log<level::ERR>(e.what());
304         *data_len = 0;
305         return IPMI_CC_UNSPECIFIED_ERROR;
306     }
307 
308     auto methodCall = bus.new_method_call(service.c_str(),
309                                           (*iter).c_str(),
310                                           ipmi::sel::logDeleteIntf,
311                                           "Delete");
312     auto reply = bus.call(methodCall);
313     if (reply.is_method_error())
314     {
315         *data_len = 0;
316         return IPMI_CC_UNSPECIFIED_ERROR;
317     }
318 
319     // Invalidate the cache of dbus entry objects.
320     cache::paths.clear();
321     memcpy(response, &delRecordID, sizeof(delRecordID));
322     *data_len = sizeof(delRecordID);
323 
324     return IPMI_CC_OK;
325 }
326 
327 ipmi_ret_t clearSEL(ipmi_netfn_t netfn, ipmi_cmd_t cmd, ipmi_request_t request,
328                     ipmi_response_t response, ipmi_data_len_t data_len,
329                     ipmi_context_t context)
330 {
331     auto requestData = reinterpret_cast<const ipmi::sel::ClearSELRequest*>
332             (request);
333 
334     if (g_sel_reserve != requestData->reservationID)
335     {
336         *data_len = 0;
337         return IPMI_CC_INVALID_RESERVATION_ID;
338     }
339 
340     if (requestData->charC != 'C' ||
341         requestData->charL != 'L' ||
342         requestData->charR != 'R')
343     {
344         *data_len = 0;
345         return IPMI_CC_INVALID_FIELD_REQUEST;
346     }
347 
348     uint8_t eraseProgress = ipmi::sel::eraseComplete;
349 
350     /*
351      * Erasure status cannot be fetched from DBUS, so always return erasure
352      * status as `erase completed`.
353      */
354     if (requestData->eraseOperation == ipmi::sel::getEraseStatus)
355     {
356         memcpy(response, &eraseProgress, sizeof(eraseProgress));
357         *data_len = sizeof(eraseProgress);
358         return IPMI_CC_OK;
359     }
360 
361     sdbusplus::bus::bus bus{ipmid_get_sd_bus_connection()};
362     auto depth = 0;
363 
364     auto mapperCall = bus.new_method_call(ipmi::sel::mapperBusName,
365                                           ipmi::sel::mapperObjPath,
366                                           ipmi::sel::mapperIntf,
367                                           "GetSubTreePaths");
368     mapperCall.append(ipmi::sel::logBasePath);
369     mapperCall.append(depth);
370     mapperCall.append(ipmi::sel::ObjectPaths({ipmi::sel::logEntryIntf}));
371 
372     auto reply = bus.call(mapperCall);
373     if (reply.is_method_error())
374     {
375         memcpy(response, &eraseProgress, sizeof(eraseProgress));
376         *data_len = sizeof(eraseProgress);
377         return IPMI_CC_OK;
378     }
379 
380     ipmi::sel::ObjectPaths objectPaths;
381     reply.read(objectPaths);
382     if (objectPaths.empty())
383     {
384         memcpy(response, &eraseProgress, sizeof(eraseProgress));
385         *data_len = sizeof(eraseProgress);
386         return IPMI_CC_OK;
387     }
388 
389     std::string service;
390 
391     try
392     {
393         service = ipmi::getService(bus,
394                                    ipmi::sel::logDeleteIntf,
395                                    objectPaths.front());
396     }
397     catch (const std::runtime_error& e)
398     {
399         log<level::ERR>(e.what());
400         *data_len = 0;
401         return IPMI_CC_UNSPECIFIED_ERROR;
402     }
403 
404     for (const auto& iter : objectPaths)
405     {
406         auto methodCall = bus.new_method_call(service.c_str(),
407                                               iter.c_str(),
408                                               ipmi::sel::logDeleteIntf,
409                                               "Delete");
410 
411         auto reply = bus.call(methodCall);
412         if (reply.is_method_error())
413         {
414             *data_len = 0;
415             return IPMI_CC_UNSPECIFIED_ERROR;
416         }
417     }
418 
419     // Invalidate the cache of dbus entry objects.
420     cache::paths.clear();
421     memcpy(response, &eraseProgress, sizeof(eraseProgress));
422     *data_len = sizeof(eraseProgress);
423     return IPMI_CC_OK;
424 }
425 
426 ipmi_ret_t ipmi_storage_get_sel_time(ipmi_netfn_t netfn, ipmi_cmd_t cmd,
427                               ipmi_request_t request, ipmi_response_t response,
428                               ipmi_data_len_t data_len, ipmi_context_t context)
429 {
430     using namespace std::chrono;
431     uint64_t host_time_usec = 0;
432     uint32_t resp = 0;
433 
434     printf("IPMI Handling GET-SEL-TIME\n");
435 
436     try
437     {
438         sdbusplus::bus::bus bus{ipmid_get_sd_bus_connection()};
439         auto service = ipmi::getService(bus, TIME_INTERFACE, HOST_TIME_PATH);
440         sdbusplus::message::variant<uint64_t> value;
441 
442         // Get host time
443         auto method = bus.new_method_call(service.c_str(),
444                                           HOST_TIME_PATH,
445                                           DBUS_PROPERTIES,
446                                           "Get");
447 
448         method.append(TIME_INTERFACE, PROPERTY_ELAPSED);
449         auto reply = bus.call(method);
450         if (reply.is_method_error())
451         {
452             log<level::ERR>("Error getting time",
453                             entry("SERVICE=%s", service.c_str()),
454                             entry("PATH=%s", HOST_TIME_PATH));
455             return IPMI_CC_UNSPECIFIED_ERROR;
456         }
457         reply.read(value);
458         host_time_usec = value.get<uint64_t>();
459     }
460     catch (InternalFailure& e)
461     {
462         log<level::ERR>(e.what());
463         return IPMI_CC_UNSPECIFIED_ERROR;
464     }
465     catch (const std::runtime_error& e)
466     {
467         log<level::ERR>(e.what());
468         return IPMI_CC_UNSPECIFIED_ERROR;
469     }
470 
471     printf("Host time: %" PRIu64 ", %s",
472            host_time_usec, getTimeString(host_time_usec));
473 
474     // Time is really long int but IPMI wants just uint32. This works okay until
475     // the number of seconds since 1970 overflows uint32 size.. Still a whole
476     // lot of time here to even think about that.
477     resp = duration_cast<seconds>(microseconds(host_time_usec)).count();
478     resp = htole32(resp);
479 
480     // From the IPMI Spec 2.0, response should be a 32-bit value
481     *data_len = sizeof(resp);
482 
483     // Pack the actual response
484     memcpy(response, &resp, *data_len);
485 
486     return IPMI_CC_OK;
487 }
488 
489 ipmi_ret_t ipmi_storage_set_sel_time(ipmi_netfn_t netfn, ipmi_cmd_t cmd,
490                               ipmi_request_t request, ipmi_response_t response,
491                               ipmi_data_len_t data_len, ipmi_context_t context)
492 {
493     using namespace std::chrono;
494     ipmi_ret_t rc = IPMI_CC_OK;
495     uint32_t secs = *static_cast<uint32_t*>(request);
496     *data_len = 0;
497 
498     printf("Handling Set-SEL-Time:[0x%X], Cmd:[0x%X], Data:[0x%X]\n",
499            netfn, cmd, secs);
500 
501     secs = le32toh(secs);
502     microseconds usec{seconds(secs)};
503 
504     printf("To Set host time: %" PRIu64 ", %s",
505            usec.count(), getTimeString(usec.count()));
506 
507     try
508     {
509         sdbusplus::bus::bus bus{ipmid_get_sd_bus_connection()};
510         auto service = ipmi::getService(bus, TIME_INTERFACE, HOST_TIME_PATH);
511         sdbusplus::message::variant<uint64_t> value{usec.count()};
512 
513         // Set host time
514         auto method = bus.new_method_call(service.c_str(),
515                                           HOST_TIME_PATH,
516                                           DBUS_PROPERTIES,
517                                           "Set");
518 
519         method.append(TIME_INTERFACE, PROPERTY_ELAPSED, value);
520         auto reply = bus.call(method);
521         if (reply.is_method_error())
522         {
523             log<level::ERR>("Error setting time",
524                             entry("SERVICE=%s", service.c_str()),
525                             entry("PATH=%s", HOST_TIME_PATH));
526             rc = IPMI_CC_UNSPECIFIED_ERROR;
527         }
528     }
529     catch (InternalFailure& e)
530     {
531         log<level::ERR>(e.what());
532         rc = IPMI_CC_UNSPECIFIED_ERROR;
533     }
534     catch (const std::runtime_error& e)
535     {
536         log<level::ERR>(e.what());
537         rc = IPMI_CC_UNSPECIFIED_ERROR;
538     }
539 
540     return rc;
541 }
542 
543 ipmi_ret_t ipmi_storage_reserve_sel(ipmi_netfn_t netfn, ipmi_cmd_t cmd,
544                               ipmi_request_t request, ipmi_response_t response,
545                               ipmi_data_len_t data_len, ipmi_context_t context)
546 {
547     ipmi_ret_t rc = IPMI_CC_OK;
548 
549     // IPMI spec, Reservation ID, the value simply increases against each execution of reserve_sel command.
550     if( ++g_sel_reserve == 0)
551         g_sel_reserve = 1;
552 
553     printf("IPMI Handling RESERVE-SEL 0x%04x\n", g_sel_reserve);
554 
555     *data_len = sizeof(g_sel_reserve);
556 
557     // Pack the actual response
558     memcpy(response, &g_sel_reserve, *data_len);
559 
560     return rc;
561 }
562 
563 ipmi_ret_t ipmi_storage_add_sel(ipmi_netfn_t netfn, ipmi_cmd_t cmd,
564                               ipmi_request_t request, ipmi_response_t response,
565                               ipmi_data_len_t data_len, ipmi_context_t context)
566 {
567 
568     ipmi_ret_t rc = IPMI_CC_OK;
569     ipmi_add_sel_request_t *p = (ipmi_add_sel_request_t*) request;
570     uint16_t recordid;
571 
572     recordid = ((uint16_t)p->eventdata[1] << 8) | p->eventdata[2];
573 
574     printf("IPMI Handling ADD-SEL for record 0x%04x\n", recordid);
575 
576     *data_len = sizeof(g_sel_reserve);
577 
578     // Pack the actual response
579     memcpy(response, &p->eventdata[1], 2);
580 
581     // Hostboot sends SEL with OEM record type 0xDE to indicate that there is
582     // a maintenance procedure associated with eSEL record.
583     static constexpr auto procedureType = 0xDE;
584     if (p->recordtype == procedureType)
585     {
586         // In the OEM record type 0xDE, byte 11 in the SEL record indicate the
587         // procedure number.
588         createProcedureLogEntry(p->sensortype);
589     }
590     else
591     {
592         send_esel(recordid);
593     }
594 
595     return rc;
596 }
597 
598 //Read FRU info area
599 ipmi_ret_t ipmi_storage_get_fru_inv_area_info(
600         ipmi_netfn_t netfn, ipmi_cmd_t cmd, ipmi_request_t request,
601         ipmi_response_t response, ipmi_data_len_t data_len,
602         ipmi_context_t context)
603 {
604     ipmi_ret_t rc = IPMI_CC_OK;
605     const FruInvenAreaInfoRequest* reqptr =
606         reinterpret_cast<const FruInvenAreaInfoRequest*>(request);
607     try
608     {
609         const auto& fruArea = getFruAreaData(reqptr->fruID);
610         auto size = static_cast<uint16_t>(fruArea.size());
611         FruInvenAreaInfoResponse resp;
612         resp.sizems = size >> 8;
613         resp.sizels = size;
614         resp.access = static_cast<uint8_t>(AccessMode::bytes);
615 
616         *data_len = sizeof(resp);
617 
618         // Pack the actual response
619         memcpy(response, &resp, *data_len);
620     }
621     catch(const InternalFailure& e)
622     {
623         rc = IPMI_CC_UNSPECIFIED_ERROR;
624         *data_len = 0;
625         log<level::ERR>(e.what());
626         report<InternalFailure>();
627     }
628     return rc;
629 }
630 
631 //Read FRU data
632 ipmi_ret_t ipmi_storage_read_fru_data(
633         ipmi_netfn_t netfn, ipmi_cmd_t cmd, ipmi_request_t request,
634         ipmi_response_t response, ipmi_data_len_t data_len,
635         ipmi_context_t context)
636 {
637     ipmi_ret_t rc = IPMI_CC_OK;
638     const ReadFruDataRequest* reqptr =
639          reinterpret_cast<const ReadFruDataRequest*>(request);
640     auto offset =
641         static_cast<uint16_t>(reqptr->offsetMS << 8 | reqptr->offsetLS);
642     try
643     {
644         const auto& fruArea = getFruAreaData(reqptr->fruID);
645         auto size = fruArea.size();
646         if ((offset + reqptr->count) > size)
647         {
648             log<level::ERR>("Invalid offset and count",
649                             entry("OFFSET=%s", offset),
650                             entry("COUNT=%s", reqptr->count),
651                             entry("SIZE_OF_FRU_AREA=%s", size));
652             return IPMI_CC_INVALID;
653         }
654         std::copy((fruArea.begin() + offset), (fruArea.begin() + reqptr->count),
655                 (static_cast<uint8_t*>(response)));
656         *data_len = reqptr->count;
657     }
658     catch (const InternalFailure& e)
659     {
660         rc = IPMI_CC_UNSPECIFIED_ERROR;
661         *data_len = 0;
662         log<level::ERR>(e.what());
663         report<InternalFailure>();
664     }
665     return rc;
666 }
667 
668 
669 void register_netfn_storage_functions()
670 {
671     // <Wildcard Command>
672     printf("Registering NetFn:[0x%X], Cmd:[0x%X]\n",NETFUN_STORAGE, IPMI_CMD_WILDCARD);
673     ipmi_register_callback(NETFUN_STORAGE, IPMI_CMD_WILDCARD, NULL, ipmi_storage_wildcard,
674                            PRIVILEGE_USER);
675 
676     // <Get SEL Info>
677     printf("Registering NetFn:[0x%X], Cmd:[0x%X]\n",NETFUN_STORAGE, IPMI_CMD_GET_SEL_INFO);
678     ipmi_register_callback(NETFUN_STORAGE, IPMI_CMD_GET_SEL_INFO, NULL, getSELInfo,
679                            PRIVILEGE_USER);
680 
681     // <Get SEL Time>
682     printf("Registering NetFn:[0x%X], Cmd:[0x%X]\n",NETFUN_STORAGE, IPMI_CMD_GET_SEL_TIME);
683     ipmi_register_callback(NETFUN_STORAGE, IPMI_CMD_GET_SEL_TIME, NULL, ipmi_storage_get_sel_time,
684                            PRIVILEGE_USER);
685 
686     // <Set SEL Time>
687     printf("Registering NetFn:[0x%X], Cmd:[0x%X]\n",NETFUN_STORAGE, IPMI_CMD_SET_SEL_TIME);
688     ipmi_register_callback(NETFUN_STORAGE, IPMI_CMD_SET_SEL_TIME, NULL, ipmi_storage_set_sel_time,
689                            PRIVILEGE_OPERATOR);
690 
691     // <Reserve SEL>
692     printf("Registering NetFn:[0x%X], Cmd:[0x%X]\n",NETFUN_STORAGE, IPMI_CMD_RESERVE_SEL);
693     ipmi_register_callback(NETFUN_STORAGE, IPMI_CMD_RESERVE_SEL, NULL, ipmi_storage_reserve_sel,
694                            PRIVILEGE_USER);
695 
696     // <Get SEL Entry>
697     printf("Registering NetFn:[0x%X], Cmd:[0x%X]\n",NETFUN_STORAGE, IPMI_CMD_GET_SEL_ENTRY);
698     ipmi_register_callback(NETFUN_STORAGE, IPMI_CMD_GET_SEL_ENTRY, NULL, getSELEntry,
699                            PRIVILEGE_USER);
700 
701     // <Delete SEL Entry>
702     printf("Registering NetFn:[0x%X], Cmd:[0x%X]\n",NETFUN_STORAGE, IPMI_CMD_DELETE_SEL);
703     ipmi_register_callback(NETFUN_STORAGE, IPMI_CMD_DELETE_SEL, NULL, deleteSELEntry,
704                            PRIVILEGE_OPERATOR);
705 
706     // <Add SEL Entry>
707     printf("Registering NetFn:[0x%X], Cmd:[0x%X]\n",NETFUN_STORAGE, IPMI_CMD_ADD_SEL);
708     ipmi_register_callback(NETFUN_STORAGE, IPMI_CMD_ADD_SEL, NULL, ipmi_storage_add_sel,
709                            PRIVILEGE_OPERATOR);
710     // <Clear SEL>
711     printf("Registering NetFn:[0x%X], Cmd:[0x%X]\n",NETFUN_STORAGE, IPMI_CMD_CLEAR_SEL);
712     ipmi_register_callback(NETFUN_STORAGE, IPMI_CMD_CLEAR_SEL, NULL, clearSEL,
713                            PRIVILEGE_OPERATOR);
714     // <Get FRU Inventory Area Info>
715     printf("Registering NetFn:[0x%X], Cmd:[0x%X]\n", NETFUN_STORAGE,
716             IPMI_CMD_GET_FRU_INV_AREA_INFO);
717     ipmi_register_callback(NETFUN_STORAGE, IPMI_CMD_GET_FRU_INV_AREA_INFO, NULL,
718             ipmi_storage_get_fru_inv_area_info, PRIVILEGE_OPERATOR);
719 
720     // <Add READ FRU Data
721     printf("Registering NetFn:[0x%X], Cmd:[0x%X]\n", NETFUN_STORAGE,
722             IPMI_CMD_READ_FRU_DATA);
723     ipmi_register_callback(NETFUN_STORAGE, IPMI_CMD_READ_FRU_DATA, NULL,
724             ipmi_storage_read_fru_data, PRIVILEGE_OPERATOR);
725 
726     ipmi::fru::registerCallbackHandler();
727     return;
728 }
729 
730