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