1 #include "storagehandler.hpp"
2 
3 #include "fruread.hpp"
4 #include "read_fru_data.hpp"
5 #include "selutility.hpp"
6 #include "sensorhandler.hpp"
7 #include "storageaddsel.hpp"
8 
9 #include <arpa/inet.h>
10 #include <mapper.h>
11 #include <systemd/sd-bus.h>
12 
13 #include <algorithm>
14 #include <chrono>
15 #include <cstdio>
16 #include <cstring>
17 #include <filesystem>
18 #include <ipmid/api.hpp>
19 #include <ipmid/utils.hpp>
20 #include <phosphor-logging/elog-errors.hpp>
21 #include <phosphor-logging/log.hpp>
22 #include <sdbusplus/server.hpp>
23 #include <string>
24 #include <variant>
25 #include <xyz/openbmc_project/Common/error.hpp>
26 
27 void register_netfn_storage_functions() __attribute__((constructor));
28 
29 unsigned int g_sel_time = 0xFFFFFFFF;
30 extern const ipmi::sensor::IdInfoMap sensors;
31 extern const FruMap frus;
32 constexpr uint8_t eventDataSize = 3;
33 namespace
34 {
35 constexpr auto TIME_INTERFACE = "xyz.openbmc_project.Time.EpochTime";
36 constexpr auto HOST_TIME_PATH = "/xyz/openbmc_project/time/host";
37 constexpr auto DBUS_PROPERTIES = "org.freedesktop.DBus.Properties";
38 constexpr auto PROPERTY_ELAPSED = "Elapsed";
39 
40 } // namespace
41 
42 namespace cache
43 {
44 /*
45  * This cache contains the object paths of the logging entries sorted in the
46  * order of the filename(numeric order). The cache is initialized by
47  * invoking readLoggingObjectPaths with the cache as the parameter. The
48  * cache is invoked in the execution of the Get SEL info and Delete SEL
49  * entry command. The Get SEL Info command is typically invoked before the
50  * Get SEL entry command, so the cache is utilized for responding to Get SEL
51  * entry command. The cache is invalidated by clearing after Delete SEL
52  * entry and Clear SEL command.
53  */
54 ipmi::sel::ObjectPaths paths;
55 
56 } // namespace cache
57 
58 using InternalFailure =
59     sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
60 using namespace phosphor::logging;
61 using namespace ipmi::fru;
62 
63 /**
64  * @enum Device access mode
65  */
66 enum class AccessMode
67 {
68     bytes, ///< Device is accessed by bytes
69     words  ///< Device is accessed by words
70 };
71 
72 ipmi_ret_t ipmi_storage_wildcard(ipmi_netfn_t netfn, ipmi_cmd_t cmd,
73                                  ipmi_request_t request,
74                                  ipmi_response_t response,
75                                  ipmi_data_len_t data_len,
76                                  ipmi_context_t context)
77 {
78     // Status code.
79     ipmi_ret_t rc = IPMI_CC_INVALID;
80     *data_len = 0;
81     return rc;
82 }
83 
84 ipmi_ret_t getSELInfo(ipmi_netfn_t netfn, ipmi_cmd_t cmd,
85                       ipmi_request_t request, ipmi_response_t response,
86                       ipmi_data_len_t data_len, ipmi_context_t context)
87 {
88     if (*data_len != 0)
89     {
90         *data_len = 0;
91         return IPMI_CC_REQ_DATA_LEN_INVALID;
92     }
93 
94     std::vector<uint8_t> outPayload(sizeof(ipmi::sel::GetSELInfoResponse));
95     auto responseData =
96         reinterpret_cast<ipmi::sel::GetSELInfoResponse*>(outPayload.data());
97 
98     responseData->selVersion = ipmi::sel::selVersion;
99     // Last erase timestamp is not available from log manager.
100     responseData->eraseTimeStamp = ipmi::sel::invalidTimeStamp;
101     responseData->operationSupport = ipmi::sel::operationSupport;
102 
103     try
104     {
105         ipmi::sel::readLoggingObjectPaths(cache::paths);
106     }
107     catch (const sdbusplus::exception::SdBusError& e)
108     {
109         // No action if reading log objects have failed for this command.
110         // readLoggingObjectPaths will throw exception if there are no log
111         // entries. The command will be responded with number of SEL entries
112         // as 0.
113     }
114 
115     responseData->entries = 0;
116     responseData->addTimeStamp = ipmi::sel::invalidTimeStamp;
117 
118     if (!cache::paths.empty())
119     {
120         responseData->entries = static_cast<uint16_t>(cache::paths.size());
121 
122         try
123         {
124             responseData->addTimeStamp = static_cast<uint32_t>(
125                 (ipmi::sel::getEntryTimeStamp(cache::paths.back()).count()));
126         }
127         catch (InternalFailure& e)
128         {
129         }
130         catch (const std::runtime_error& e)
131         {
132             log<level::ERR>(e.what());
133         }
134     }
135 
136     std::memcpy(response, outPayload.data(), outPayload.size());
137     *data_len = outPayload.size();
138 
139     return IPMI_CC_OK;
140 }
141 
142 ipmi_ret_t getSELEntry(ipmi_netfn_t netfn, ipmi_cmd_t cmd,
143                        ipmi_request_t request, ipmi_response_t response,
144                        ipmi_data_len_t data_len, ipmi_context_t context)
145 {
146     if (*data_len != sizeof(ipmi::sel::GetSELEntryRequest))
147     {
148         *data_len = 0;
149         return IPMI_CC_REQ_DATA_LEN_INVALID;
150     }
151 
152     auto requestData =
153         reinterpret_cast<const ipmi::sel::GetSELEntryRequest*>(request);
154 
155     if (requestData->reservationID != 0)
156     {
157         if (!checkSELReservation(requestData->reservationID))
158         {
159             *data_len = 0;
160             return IPMI_CC_INVALID_RESERVATION_ID;
161         }
162     }
163 
164     if (cache::paths.empty())
165     {
166         *data_len = 0;
167         return IPMI_CC_SENSOR_INVALID;
168     }
169 
170     ipmi::sel::ObjectPaths::const_iterator iter;
171 
172     // Check for the requested SEL Entry.
173     if (requestData->selRecordID == ipmi::sel::firstEntry)
174     {
175         iter = cache::paths.begin();
176     }
177     else if (requestData->selRecordID == ipmi::sel::lastEntry)
178     {
179         iter = cache::paths.end();
180     }
181     else
182     {
183         std::string objPath = std::string(ipmi::sel::logBasePath) + "/" +
184                               std::to_string(requestData->selRecordID);
185 
186         iter = std::find(cache::paths.begin(), cache::paths.end(), objPath);
187         if (iter == cache::paths.end())
188         {
189             *data_len = 0;
190             return IPMI_CC_SENSOR_INVALID;
191         }
192     }
193 
194     ipmi::sel::GetSELEntryResponse record{};
195 
196     // Convert the log entry into SEL record.
197     try
198     {
199         record = ipmi::sel::convertLogEntrytoSEL(*iter);
200     }
201     catch (InternalFailure& e)
202     {
203         *data_len = 0;
204         return IPMI_CC_UNSPECIFIED_ERROR;
205     }
206     catch (const std::runtime_error& e)
207     {
208         log<level::ERR>(e.what());
209         *data_len = 0;
210         return IPMI_CC_UNSPECIFIED_ERROR;
211     }
212 
213     // Identify the next SEL record ID
214     if (iter != cache::paths.end())
215     {
216         ++iter;
217         if (iter == cache::paths.end())
218         {
219             record.nextRecordID = ipmi::sel::lastEntry;
220         }
221         else
222         {
223             namespace fs = std::filesystem;
224             fs::path path(*iter);
225             record.nextRecordID = static_cast<uint16_t>(
226                 std::stoul(std::string(path.filename().c_str())));
227         }
228     }
229     else
230     {
231         record.nextRecordID = ipmi::sel::lastEntry;
232     }
233 
234     if (requestData->readLength == ipmi::sel::entireRecord)
235     {
236         std::memcpy(response, &record, sizeof(record));
237         *data_len = sizeof(record);
238     }
239     else
240     {
241         if (requestData->offset >= ipmi::sel::selRecordSize ||
242             requestData->readLength > ipmi::sel::selRecordSize)
243         {
244             *data_len = 0;
245             return IPMI_CC_INVALID_FIELD_REQUEST;
246         }
247 
248         auto diff = ipmi::sel::selRecordSize - requestData->offset;
249         auto readLength =
250             std::min(diff, static_cast<int>(requestData->readLength));
251 
252         std::memcpy(response, &record.nextRecordID,
253                     sizeof(record.nextRecordID));
254         std::memcpy(static_cast<uint8_t*>(response) +
255                         sizeof(record.nextRecordID),
256                     &record.recordID + requestData->offset, readLength);
257         *data_len = sizeof(record.nextRecordID) + readLength;
258     }
259 
260     return IPMI_CC_OK;
261 }
262 
263 /** @brief implements the delete SEL entry command
264  * @request
265  *   - reservationID; // reservation ID.
266  *   - selRecordID;   // SEL record ID.
267  *
268  *  @returns ipmi completion code plus response data
269  *   - Record ID of the deleted record
270  */
271 ipmi::RspType<uint16_t // deleted record ID
272               >
273     deleteSELEntry(uint16_t reservationID, uint16_t selRecordID)
274 {
275 
276     namespace fs = std::filesystem;
277 
278     if (!checkSELReservation(reservationID))
279     {
280         return ipmi::responseInvalidReservationId();
281     }
282 
283     // Per the IPMI spec, need to cancel the reservation when a SEL entry is
284     // deleted
285     cancelSELReservation();
286 
287     try
288     {
289         ipmi::sel::readLoggingObjectPaths(cache::paths);
290     }
291     catch (const sdbusplus::exception::SdBusError& e)
292     {
293         // readLoggingObjectPaths will throw exception if there are no error
294         // log entries.
295         return ipmi::responseSensorInvalid();
296     }
297 
298     if (cache::paths.empty())
299     {
300         return ipmi::responseSensorInvalid();
301     }
302 
303     ipmi::sel::ObjectPaths::const_iterator iter;
304     uint16_t delRecordID = 0;
305 
306     if (selRecordID == ipmi::sel::firstEntry)
307     {
308         iter = cache::paths.begin();
309         fs::path path(*iter);
310         delRecordID = static_cast<uint16_t>(
311             std::stoul(std::string(path.filename().c_str())));
312     }
313     else if (selRecordID == ipmi::sel::lastEntry)
314     {
315         iter = cache::paths.end();
316         fs::path path(*iter);
317         delRecordID = static_cast<uint16_t>(
318             std::stoul(std::string(path.filename().c_str())));
319     }
320     else
321     {
322         std::string objPath = std::string(ipmi::sel::logBasePath) + "/" +
323                               std::to_string(selRecordID);
324 
325         iter = std::find(cache::paths.begin(), cache::paths.end(), objPath);
326         if (iter == cache::paths.end())
327         {
328             return ipmi::responseSensorInvalid();
329         }
330         delRecordID = selRecordID;
331     }
332 
333     sdbusplus::bus::bus bus{ipmid_get_sd_bus_connection()};
334     std::string service;
335 
336     try
337     {
338         service = ipmi::getService(bus, ipmi::sel::logDeleteIntf, *iter);
339     }
340     catch (const std::runtime_error& e)
341     {
342         log<level::ERR>(e.what());
343         return ipmi::responseUnspecifiedError();
344     }
345 
346     auto methodCall = bus.new_method_call(service.c_str(), (*iter).c_str(),
347                                           ipmi::sel::logDeleteIntf, "Delete");
348     auto reply = bus.call(methodCall);
349     if (reply.is_method_error())
350     {
351         return ipmi::responseUnspecifiedError();
352     }
353 
354     // Invalidate the cache of dbus entry objects.
355     cache::paths.clear();
356 
357     return ipmi::responseSuccess(delRecordID);
358 }
359 
360 /** @brief implements the Clear SEL command
361  * @request
362  *   - reservationID   // Reservation ID.
363  *   - clr             // char array { 'C'(0x43h), 'L'(0x4Ch), 'R'(0x52h) }
364  *   - eraseOperation; // requested operation.
365  *
366  *  @returns ipmi completion code plus response data
367  *   - erase status
368  */
369 
370 ipmi::RspType<uint8_t // erase status
371               >
372     clearSEL(uint16_t reservationID, const std::array<char, 3>& clr,
373              uint8_t eraseOperation)
374 {
375     static constexpr std::array<char, 3> clrOk = {'C', 'L', 'R'};
376     if (clr != clrOk)
377     {
378         return ipmi::responseInvalidFieldRequest();
379     }
380 
381     if (!checkSELReservation(reservationID))
382     {
383         return ipmi::responseInvalidReservationId();
384     }
385 
386     /*
387      * Erasure status cannot be fetched from DBUS, so always return erasure
388      * status as `erase completed`.
389      */
390     if (eraseOperation == ipmi::sel::getEraseStatus)
391     {
392         return ipmi::responseSuccess(
393             static_cast<uint8_t>(ipmi::sel::eraseComplete));
394     }
395 
396     // Per the IPMI spec, need to cancel any reservation when the SEL is cleared
397     cancelSELReservation();
398 
399     sdbusplus::bus::bus bus{ipmid_get_sd_bus_connection()};
400     ipmi::sel::ObjectPaths objectPaths;
401     auto depth = 0;
402 
403     auto mapperCall =
404         bus.new_method_call(ipmi::sel::mapperBusName, ipmi::sel::mapperObjPath,
405                             ipmi::sel::mapperIntf, "GetSubTreePaths");
406     mapperCall.append(ipmi::sel::logBasePath);
407     mapperCall.append(depth);
408     mapperCall.append(ipmi::sel::ObjectPaths({ipmi::sel::logEntryIntf}));
409 
410     try
411     {
412         auto reply = bus.call(mapperCall);
413         if (reply.is_method_error())
414         {
415             return ipmi::responseSuccess(
416                 static_cast<uint8_t>(ipmi::sel::eraseComplete));
417         }
418 
419         reply.read(objectPaths);
420         if (objectPaths.empty())
421         {
422             return ipmi::responseSuccess(
423                 static_cast<uint8_t>(ipmi::sel::eraseComplete));
424         }
425     }
426     catch (const sdbusplus::exception::SdBusError& e)
427     {
428         return ipmi::responseSuccess(
429             static_cast<uint8_t>(ipmi::sel::eraseComplete));
430     }
431 
432     std::string service;
433 
434     try
435     {
436         service = ipmi::getService(bus, ipmi::sel::logDeleteIntf,
437                                    objectPaths.front());
438     }
439     catch (const std::runtime_error& e)
440     {
441         log<level::ERR>(e.what());
442         return ipmi::responseUnspecifiedError();
443     }
444 
445     for (const auto& iter : objectPaths)
446     {
447         auto methodCall = bus.new_method_call(
448             service.c_str(), iter.c_str(), ipmi::sel::logDeleteIntf, "Delete");
449 
450         auto reply = bus.call(methodCall);
451         if (reply.is_method_error())
452         {
453             return ipmi::responseUnspecifiedError();
454         }
455     }
456 
457     // Invalidate the cache of dbus entry objects.
458     cache::paths.clear();
459     return ipmi::responseSuccess(
460         static_cast<uint8_t>(ipmi::sel::eraseComplete));
461 }
462 
463 /** @brief implements the get SEL time command
464  *  @returns IPMI completion code plus response data
465  *   -current time
466  */
467 ipmi::RspType<uint32_t> // current time
468     ipmiStorageGetSelTime()
469 {
470     using namespace std::chrono;
471     uint64_t host_time_usec = 0;
472     std::stringstream hostTime;
473 
474     try
475     {
476         sdbusplus::bus::bus bus{ipmid_get_sd_bus_connection()};
477         auto service = ipmi::getService(bus, TIME_INTERFACE, HOST_TIME_PATH);
478         std::variant<uint64_t> value;
479 
480         // Get host time
481         auto method = bus.new_method_call(service.c_str(), HOST_TIME_PATH,
482                                           DBUS_PROPERTIES, "Get");
483 
484         method.append(TIME_INTERFACE, PROPERTY_ELAPSED);
485         auto reply = bus.call(method);
486         if (reply.is_method_error())
487         {
488             log<level::ERR>("Error getting time",
489                             entry("SERVICE=%s", service.c_str()),
490                             entry("PATH=%s", HOST_TIME_PATH));
491             return ipmi::responseUnspecifiedError();
492         }
493         reply.read(value);
494         host_time_usec = std::get<uint64_t>(value);
495     }
496     catch (InternalFailure& e)
497     {
498         log<level::ERR>(e.what());
499         return ipmi::responseUnspecifiedError();
500     }
501     catch (const std::runtime_error& e)
502     {
503         log<level::ERR>(e.what());
504         return ipmi::responseUnspecifiedError();
505     }
506 
507     hostTime << "Host time:"
508              << duration_cast<seconds>(microseconds(host_time_usec)).count();
509     log<level::DEBUG>(hostTime.str().c_str());
510 
511     // Time is really long int but IPMI wants just uint32. This works okay until
512     // the number of seconds since 1970 overflows uint32 size.. Still a whole
513     // lot of time here to even think about that.
514     return ipmi::responseSuccess(
515         duration_cast<seconds>(microseconds(host_time_usec)).count());
516 }
517 
518 /** @brief implements the set SEL time command
519  *  @param selDeviceTime - epoch time
520  *        -local time as the number of seconds from 00:00:00, January 1, 1970
521  *  @returns IPMI completion code
522  */
523 ipmi::RspType<> ipmiStorageSetSelTime(uint32_t selDeviceTime)
524 {
525     using namespace std::chrono;
526     microseconds usec{seconds(selDeviceTime)};
527 
528     try
529     {
530         sdbusplus::bus::bus bus{ipmid_get_sd_bus_connection()};
531         auto service = ipmi::getService(bus, TIME_INTERFACE, HOST_TIME_PATH);
532         std::variant<uint64_t> value{usec.count()};
533 
534         // Set host time
535         auto method = bus.new_method_call(service.c_str(), HOST_TIME_PATH,
536                                           DBUS_PROPERTIES, "Set");
537 
538         method.append(TIME_INTERFACE, PROPERTY_ELAPSED, value);
539         auto reply = bus.call(method);
540         if (reply.is_method_error())
541         {
542             log<level::ERR>("Error setting time",
543                             entry("SERVICE=%s", service.c_str()),
544                             entry("PATH=%s", HOST_TIME_PATH));
545             return ipmi::responseUnspecifiedError();
546         }
547     }
548     catch (InternalFailure& e)
549     {
550         log<level::ERR>(e.what());
551         return ipmi::responseUnspecifiedError();
552     }
553     catch (const std::runtime_error& e)
554     {
555         log<level::ERR>(e.what());
556         return ipmi::responseUnspecifiedError();
557     }
558 
559     return ipmi::responseSuccess();
560 }
561 
562 ipmi_ret_t ipmi_storage_reserve_sel(ipmi_netfn_t netfn, ipmi_cmd_t cmd,
563                                     ipmi_request_t request,
564                                     ipmi_response_t response,
565                                     ipmi_data_len_t data_len,
566                                     ipmi_context_t context)
567 {
568     if (*data_len != 0)
569     {
570         *data_len = 0;
571         return IPMI_CC_REQ_DATA_LEN_INVALID;
572     }
573 
574     ipmi_ret_t rc = IPMI_CC_OK;
575     unsigned short selResID = reserveSel();
576 
577     *data_len = sizeof(selResID);
578 
579     // Pack the actual response
580     std::memcpy(response, &selResID, *data_len);
581 
582     return rc;
583 }
584 
585 /** @brief implements the Add SEL entry command
586  * @request
587  *
588  *   - recordID      ID used for SEL Record access
589  *   - recordType    Record Type
590  *   - timeStamp     Time when event was logged. LS byte first
591  *   - generatorID   software ID if event was generated from
592  *                   system software
593  *   - evmRev        event message format version
594  *   - sensorType    sensor type code for service that generated
595  *                   the event
596  *   - sensorNumber  number of sensors that generated the event
597  *   - eventDir     event dir
598  *   - eventData    event data field contents
599  *
600  *  @returns ipmi completion code plus response data
601  *   - RecordID of the Added SEL entry
602  */
603 ipmi::RspType<uint16_t // recordID of the Added SEL entry
604               >
605     ipmiStorageAddSEL(uint16_t recordID, uint8_t recordType, uint32_t timeStamp,
606                       uint16_t generatorID, uint8_t evmRev, uint8_t sensorType,
607                       uint8_t sensorNumber, uint8_t eventDir,
608                       std::array<uint8_t, eventDataSize> eventData)
609 {
610     // Per the IPMI spec, need to cancel the reservation when a SEL entry is
611     // added
612     cancelSELReservation();
613     // Hostboot sends SEL with OEM record type 0xDE to indicate that there is
614     // a maintenance procedure associated with eSEL record.
615     static constexpr auto procedureType = 0xDE;
616     if (recordType == procedureType)
617     {
618         // In the OEM record type 0xDE, byte 11 in the SEL record indicate the
619         // procedure number.
620         createProcedureLogEntry(sensorType);
621     }
622 
623     return ipmi::responseSuccess(recordID);
624 }
625 
626 /** @brief implements the get FRU Inventory Area Info command
627  *
628  *  @returns IPMI completion code plus response data
629  *   - FRU Inventory area size in bytes,
630  *   - access bit
631  **/
632 ipmi::RspType<uint16_t, // FRU Inventory area size in bytes,
633               uint8_t   // access size (bytes / words)
634               >
635     ipmiStorageGetFruInvAreaInfo(uint8_t fruID)
636 {
637 
638     auto iter = frus.find(fruID);
639     if (iter == frus.end())
640     {
641         return ipmi::responseSensorInvalid();
642     }
643 
644     try
645     {
646         return ipmi::responseSuccess(
647             static_cast<uint16_t>(getFruAreaData(fruID).size()),
648             static_cast<uint8_t>(AccessMode::bytes));
649     }
650     catch (const InternalFailure& e)
651     {
652         log<level::ERR>(e.what());
653         return ipmi::responseUnspecifiedError();
654     }
655 }
656 
657 // Read FRU data
658 ipmi_ret_t ipmi_storage_read_fru_data(ipmi_netfn_t netfn, ipmi_cmd_t cmd,
659                                       ipmi_request_t request,
660                                       ipmi_response_t response,
661                                       ipmi_data_len_t data_len,
662                                       ipmi_context_t context)
663 {
664     ipmi_ret_t rc = IPMI_CC_OK;
665     const ReadFruDataRequest* reqptr =
666         reinterpret_cast<const ReadFruDataRequest*>(request);
667     auto resptr = reinterpret_cast<ReadFruDataResponse*>(response);
668 
669     auto iter = frus.find(reqptr->fruID);
670     if (iter == frus.end())
671     {
672         *data_len = 0;
673         return IPMI_CC_SENSOR_INVALID;
674     }
675 
676     auto offset =
677         static_cast<uint16_t>(reqptr->offsetMS << 8 | reqptr->offsetLS);
678     try
679     {
680         const auto& fruArea = getFruAreaData(reqptr->fruID);
681         auto size = fruArea.size();
682 
683         if (offset >= size)
684         {
685             return IPMI_CC_PARM_OUT_OF_RANGE;
686         }
687 
688         // Write the count of response data.
689         if ((offset + reqptr->count) <= size)
690         {
691             resptr->count = reqptr->count;
692         }
693         else
694         {
695             resptr->count = size - offset;
696         }
697 
698         std::copy((fruArea.begin() + offset),
699                   (fruArea.begin() + offset + resptr->count), resptr->data);
700 
701         *data_len = resptr->count + 1; // additional one byte for count
702     }
703     catch (const InternalFailure& e)
704     {
705         rc = IPMI_CC_UNSPECIFIED_ERROR;
706         *data_len = 0;
707         log<level::ERR>(e.what());
708     }
709     return rc;
710 }
711 
712 ipmi_ret_t ipmi_get_repository_info(ipmi_netfn_t netfn, ipmi_cmd_t cmd,
713                                     ipmi_request_t request,
714                                     ipmi_response_t response,
715                                     ipmi_data_len_t data_len,
716                                     ipmi_context_t context)
717 {
718     constexpr auto sdrVersion = 0x51;
719     auto responseData = reinterpret_cast<GetRepositoryInfoResponse*>(response);
720 
721     std::memset(responseData, 0, sizeof(GetRepositoryInfoResponse));
722 
723     responseData->sdrVersion = sdrVersion;
724 
725     uint16_t records = frus.size() + sensors.size();
726     responseData->recordCountMs = records >> 8;
727     responseData->recordCountLs = records;
728 
729     responseData->freeSpace[0] = 0xFF;
730     responseData->freeSpace[1] = 0xFF;
731 
732     *data_len = sizeof(GetRepositoryInfoResponse);
733 
734     return IPMI_CC_OK;
735 }
736 
737 void register_netfn_storage_functions()
738 {
739     // <Wildcard Command>
740     ipmi_register_callback(NETFUN_STORAGE, IPMI_CMD_WILDCARD, NULL,
741                            ipmi_storage_wildcard, PRIVILEGE_USER);
742 
743     // <Get SEL Info>
744     ipmi_register_callback(NETFUN_STORAGE, IPMI_CMD_GET_SEL_INFO, NULL,
745                            getSELInfo, PRIVILEGE_USER);
746 
747     // <Get SEL Time>
748     ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
749                           ipmi::storage::cmdGetSelTime, ipmi::Privilege::User,
750                           ipmiStorageGetSelTime);
751 
752     // <Set SEL Time>
753     ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
754                           ipmi::storage::cmdSetSelTime,
755                           ipmi::Privilege::Operator, ipmiStorageSetSelTime);
756 
757     // <Reserve SEL>
758     ipmi_register_callback(NETFUN_STORAGE, IPMI_CMD_RESERVE_SEL, NULL,
759                            ipmi_storage_reserve_sel, PRIVILEGE_USER);
760 
761     // <Get SEL Entry>
762     ipmi_register_callback(NETFUN_STORAGE, IPMI_CMD_GET_SEL_ENTRY, NULL,
763                            getSELEntry, PRIVILEGE_USER);
764 
765     // <Delete SEL Entry>
766     ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
767                           ipmi::storage::cmdDeleteSelEntry,
768                           ipmi::Privilege::Operator, deleteSELEntry);
769 
770     // <Add SEL Entry>
771     ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
772                           ipmi::storage::cmdAddSelEntry,
773                           ipmi::Privilege::Operator, ipmiStorageAddSEL);
774 
775     // <Clear SEL>
776     ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
777                           ipmi::storage::cmdClearSel, ipmi::Privilege::Operator,
778                           clearSEL);
779 
780     // <Get FRU Inventory Area Info>
781     ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
782                           ipmi::storage::cmdGetFruInventoryAreaInfo,
783                           ipmi::Privilege::User, ipmiStorageGetFruInvAreaInfo);
784 
785     // <READ FRU Data>
786     ipmi_register_callback(NETFUN_STORAGE, IPMI_CMD_READ_FRU_DATA, NULL,
787                            ipmi_storage_read_fru_data, PRIVILEGE_USER);
788 
789     // <Get Repository Info>
790     ipmi_register_callback(NETFUN_STORAGE, IPMI_CMD_GET_REPOSITORY_INFO,
791                            nullptr, ipmi_get_repository_info, PRIVILEGE_USER);
792 
793     // <Reserve SDR Repository>
794     ipmi_register_callback(NETFUN_STORAGE, IPMI_CMD_RESERVE_SDR, nullptr,
795                            ipmi_sen_reserve_sdr, PRIVILEGE_USER);
796 
797     // <Get SDR>
798     ipmi_register_callback(NETFUN_STORAGE, IPMI_CMD_GET_SDR, nullptr,
799                            ipmi_sen_get_sdr, PRIVILEGE_USER);
800 
801     ipmi::fru::registerCallbackHandler();
802     return;
803 }
804