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.h>
19 
20 #include <boost/container/flat_map.hpp>
21 #include <commandutils.hpp>
22 #include <ipmid/utils.hpp>
23 #include <phosphor-logging/log.hpp>
24 #include <sdbusplus/message/types.hpp>
25 #include <sdbusplus/timer.hpp>
26 #include <sensorutils.hpp>
27 #include <storagecommands.hpp>
28 #include <unordered_map>
29 
30 #include <iostream>
31 
32 namespace ipmi
33 {
34 
35 namespace storage
36 {
37 void registerStorageFunctions() __attribute__((constructor));
38 
39 constexpr static const size_t maxMessageSize = 64;
40 constexpr static const size_t maxFruSdrNameSize = 16;
41 static constexpr int sensorMapUpdatePeriod = 2;
42 using SensorMap = std::map<std::string, std::map<std::string, DbusVariant>>;
43 
44 using ManagedObjectSensor =
45     std::map<sdbusplus::message::object_path,
46              std::map<std::string, std::map<std::string, DbusVariant>>>;
47 
48 static uint16_t sdrReservationID;
49 
50 static boost::container::flat_map<std::string, ManagedObjectSensor> SensorCache;
51 static SensorSubTree sensorTree;
52 
53 void registerSensorFunctions() __attribute__((constructor));
54 using ManagedObjectType = boost::container::flat_map<
55     sdbusplus::message::object_path,
56     boost::container::flat_map<
57         std::string, boost::container::flat_map<std::string, DbusVariant>>>;
58 using ManagedEntry = std::pair<
59     sdbusplus::message::object_path,
60     boost::container::flat_map<
61         std::string, boost::container::flat_map<std::string, DbusVariant>>>;
62 
63 constexpr static const char* fruDeviceServiceName =
64     "xyz.openbmc_project.FruDevice";
65 constexpr static const size_t cacheTimeoutSeconds = 10;
66 
67 static std::vector<uint8_t> fruCache;
68 static uint8_t cacheBus = 0xFF;
69 static uint8_t cacheAddr = 0XFF;
70 
71 std::unique_ptr<phosphor::Timer> cacheTimer = nullptr;
72 
73 // we unfortunately have to build a map of hashes in case there is a
74 // collision to verify our dev-id
75 boost::container::flat_map<uint8_t, std::pair<uint8_t, uint8_t>> deviceHashes;
76 
77 static sdbusplus::bus_t dbus(ipmid_get_sd_bus_connection());
78 
79 using InterfaceName = std::string;
80 using PropertyName = std::string;
81 using ThresholdStr = std::string;
82 
83 enum class AlarmType
84 {
85     low,
86     high
87 };
88 
89 struct Property
90 {
91     PropertyName name;
92     ThresholdStr threshold;
93 };
94 
95 const std::vector<InterfaceName> thresholdCheckedOrder{
96     "xyz.openbmc_project.Sensor.Threshold.HardShutdown",
97     "xyz.openbmc_project.Sensor.Threshold.SoftShutdown",
98     "xyz.openbmc_project.Sensor.Threshold.Critical",
99     "xyz.openbmc_project.Sensor.Threshold.Warning"};
100 
101 const std::unordered_map<std::string, std::map<AlarmType, Property>>
102     alarmProperties{
103         {"xyz.openbmc_project.Sensor.Threshold.HardShutdown",
104          {{AlarmType::low, Property{"HardShutdownAlarmLow", "LNR"}},
105           {AlarmType::high, Property{"HardShutdownAlarmHigh", "UNR"}}}},
106 
107         {"xyz.openbmc_project.Sensor.Threshold.SoftShutdown",
108          {{AlarmType::low, Property{"SoftShutdownAlarmLow", "LNR"}},
109           {AlarmType::high, Property{"SoftShutdownAlarmHigh", "UNR"}}}},
110 
111         {"xyz.openbmc_project.Sensor.Threshold.Critical",
112          {{AlarmType::low, Property{"CriticalAlarmLow", "LCR"}},
113           {AlarmType::high, Property{"CriticalAlarmHigh", "UCR"}}}},
114 
115         {"xyz.openbmc_project.Sensor.Threshold.Warning",
116          {{AlarmType::low, Property{"WarningAlarmLow", "LNC"}},
117           {AlarmType::high, Property{"WarningAlarmHigh", "UNC"}}}},
118     };
119 
120 static bool getSensorMap(std::string sensorConnection, std::string sensorPath,
121                          SensorMap& sensorMap)
122 {
123     static boost::container::flat_map<
124         std::string, std::chrono::time_point<std::chrono::steady_clock>>
125         updateTimeMap;
126 
127     auto updateFind = updateTimeMap.find(sensorConnection);
128     auto lastUpdate = std::chrono::time_point<std::chrono::steady_clock>();
129     if (updateFind != updateTimeMap.end())
130     {
131         lastUpdate = updateFind->second;
132     }
133 
134     auto now = std::chrono::steady_clock::now();
135 
136     if (std::chrono::duration_cast<std::chrono::seconds>(now - lastUpdate)
137             .count() > sensorMapUpdatePeriod)
138     {
139         updateTimeMap[sensorConnection] = now;
140 
141         auto managedObj = dbus.new_method_call(
142             sensorConnection.c_str(), "/xyz/openbmc_project/sensors",
143             "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
144 
145         ManagedObjectSensor managedObjects;
146         try
147         {
148             auto reply = dbus.call(managedObj);
149             reply.read(managedObjects);
150         }
151         catch (const sdbusplus::exception_t&)
152         {
153             phosphor::logging::log<phosphor::logging::level::ERR>(
154                 "Error getting managed objects from connection",
155                 phosphor::logging::entry("CONNECTION=%s",
156                                          sensorConnection.c_str()));
157             return false;
158         }
159 
160         SensorCache[sensorConnection] = managedObjects;
161     }
162     auto connection = SensorCache.find(sensorConnection);
163     if (connection == SensorCache.end())
164     {
165         return false;
166     }
167     auto path = connection->second.find(sensorPath);
168     if (path == connection->second.end())
169     {
170         return false;
171     }
172     sensorMap = path->second;
173 
174     return true;
175 }
176 
177 bool writeFru()
178 {
179     sdbusplus::message_t writeFru = dbus.new_method_call(
180         fruDeviceServiceName, "/xyz/openbmc_project/FruDevice",
181         "xyz.openbmc_project.FruDeviceManager", "WriteFru");
182     writeFru.append(cacheBus, cacheAddr, fruCache);
183     try
184     {
185         sdbusplus::message_t writeFruResp = dbus.call(writeFru);
186     }
187     catch (const sdbusplus::exception_t&)
188     {
189         // todo: log sel?
190         phosphor::logging::log<phosphor::logging::level::ERR>(
191             "error writing fru");
192         return false;
193     }
194     return true;
195 }
196 
197 void createTimer()
198 {
199     if (cacheTimer == nullptr)
200     {
201         cacheTimer = std::make_unique<phosphor::Timer>(writeFru);
202     }
203 }
204 
205 ipmi_ret_t replaceCacheFru(uint8_t devId)
206 {
207     static uint8_t lastDevId = 0xFF;
208 
209     bool timerRunning = (cacheTimer != nullptr) && !cacheTimer->isExpired();
210     if (lastDevId == devId && timerRunning)
211     {
212         return IPMI_CC_OK; // cache already up to date
213     }
214     // if timer is running, stop it and writeFru manually
215     else if (timerRunning)
216     {
217         cacheTimer->stop();
218         writeFru();
219     }
220 
221     sdbusplus::message_t getObjects = dbus.new_method_call(
222         fruDeviceServiceName, "/", "org.freedesktop.DBus.ObjectManager",
223         "GetManagedObjects");
224     ManagedObjectType frus;
225     try
226     {
227         sdbusplus::message_t resp = dbus.call(getObjects);
228         resp.read(frus);
229     }
230     catch (const sdbusplus::exception_t&)
231     {
232         phosphor::logging::log<phosphor::logging::level::ERR>(
233             "replaceCacheFru: error getting managed objects");
234         return IPMI_CC_RESPONSE_ERROR;
235     }
236 
237     deviceHashes.clear();
238 
239     // hash the object paths to create unique device id's. increment on
240     // collision
241     [[maybe_unused]] std::hash<std::string> hasher;
242     for (const auto& fru : frus)
243     {
244         auto fruIface = fru.second.find("xyz.openbmc_project.FruDevice");
245         if (fruIface == fru.second.end())
246         {
247             continue;
248         }
249 
250         auto busFind = fruIface->second.find("BUS");
251         auto addrFind = fruIface->second.find("ADDRESS");
252         if (busFind == fruIface->second.end() ||
253             addrFind == fruIface->second.end())
254         {
255             phosphor::logging::log<phosphor::logging::level::INFO>(
256                 "fru device missing Bus or Address",
257                 phosphor::logging::entry("FRU=%s", fru.first.str.c_str()));
258             continue;
259         }
260 
261         uint8_t fruBus = std::get<uint32_t>(busFind->second);
262         uint8_t fruAddr = std::get<uint32_t>(addrFind->second);
263 
264         uint8_t fruHash = 0;
265         // Need to revise this strategy for dev id
266         /*
267         if (fruBus != 0 || fruAddr != 0)
268         {
269           fruHash = hasher(fru.first.str);
270           // can't be 0xFF based on spec, and 0 is reserved for baseboard
271           if (fruHash == 0 || fruHash == 0xFF)
272           {
273             fruHash = 1;
274           }
275         }
276         */
277         std::pair<uint8_t, uint8_t> newDev(fruBus, fruAddr);
278 
279         bool emplacePassed = false;
280         while (!emplacePassed)
281         {
282             auto resp = deviceHashes.emplace(fruHash, newDev);
283             emplacePassed = resp.second;
284             if (!emplacePassed)
285             {
286                 fruHash++;
287                 // can't be 0xFF based on spec, and 0 is reserved for
288                 // baseboard
289                 if (fruHash == 0XFF)
290                 {
291                     fruHash = 0x1;
292                 }
293             }
294         }
295     }
296     auto deviceFind = deviceHashes.find(devId);
297     if (deviceFind == deviceHashes.end())
298     {
299         return IPMI_CC_SENSOR_INVALID;
300     }
301 
302     fruCache.clear();
303     sdbusplus::message_t getRawFru = dbus.new_method_call(
304         fruDeviceServiceName, "/xyz/openbmc_project/FruDevice",
305         "xyz.openbmc_project.FruDeviceManager", "GetRawFru");
306     cacheBus = deviceFind->second.first;
307     cacheAddr = deviceFind->second.second;
308     getRawFru.append(cacheBus, cacheAddr);
309     try
310     {
311         sdbusplus::message_t getRawResp = dbus.call(getRawFru);
312         getRawResp.read(fruCache);
313     }
314     catch (const sdbusplus::exception_t&)
315     {
316         lastDevId = 0xFF;
317         cacheBus = 0xFF;
318         cacheAddr = 0xFF;
319         return IPMI_CC_RESPONSE_ERROR;
320     }
321 
322     lastDevId = devId;
323     return IPMI_CC_OK;
324 }
325 
326 ipmi_ret_t ipmiStorageReadFRUData(ipmi_netfn_t, ipmi_cmd_t,
327                                   ipmi_request_t request,
328                                   ipmi_response_t response,
329                                   ipmi_data_len_t dataLen, ipmi_context_t)
330 {
331     if (*dataLen != 4)
332     {
333         *dataLen = 0;
334         return IPMI_CC_REQ_DATA_LEN_INVALID;
335     }
336     *dataLen = 0; // default to 0 in case of an error
337 
338     auto req = static_cast<GetFRUAreaReq*>(request);
339 
340     if (req->countToRead > maxMessageSize - 1)
341     {
342         return IPMI_CC_INVALID_FIELD_REQUEST;
343     }
344     ipmi_ret_t status = replaceCacheFru(req->fruDeviceID);
345 
346     if (status != IPMI_CC_OK)
347     {
348         return status;
349     }
350 
351     size_t fromFRUByteLen = 0;
352     if (req->countToRead + req->fruInventoryOffset < fruCache.size())
353     {
354         fromFRUByteLen = req->countToRead;
355     }
356     else if (fruCache.size() > req->fruInventoryOffset)
357     {
358         fromFRUByteLen = fruCache.size() - req->fruInventoryOffset;
359     }
360     size_t padByteLen = req->countToRead - fromFRUByteLen;
361     uint8_t* respPtr = static_cast<uint8_t*>(response);
362     *respPtr = req->countToRead;
363     std::copy(fruCache.begin() + req->fruInventoryOffset,
364               fruCache.begin() + req->fruInventoryOffset + fromFRUByteLen,
365               ++respPtr);
366     // if longer than the fru is requested, fill with 0xFF
367     if (padByteLen)
368     {
369         respPtr += fromFRUByteLen;
370         std::fill(respPtr, respPtr + padByteLen, 0xFF);
371     }
372     *dataLen = fromFRUByteLen + 1;
373 
374     return IPMI_CC_OK;
375 }
376 
377 ipmi_ret_t ipmiStorageWriteFRUData(ipmi_netfn_t, ipmi_cmd_t,
378                                    ipmi_request_t request,
379                                    ipmi_response_t response,
380                                    ipmi_data_len_t dataLen, ipmi_context_t)
381 {
382     if (*dataLen < 4 ||
383         *dataLen >=
384             0xFF + 3) // count written return is one byte, so limit to one
385                       // byte of data after the three request data bytes
386     {
387         *dataLen = 0;
388         return IPMI_CC_REQ_DATA_LEN_INVALID;
389     }
390 
391     auto req = static_cast<WriteFRUDataReq*>(request);
392     size_t writeLen = *dataLen - 3;
393     *dataLen = 0; // default to 0 in case of an error
394 
395     ipmi_ret_t status = replaceCacheFru(req->fruDeviceID);
396     if (status != IPMI_CC_OK)
397     {
398         return status;
399     }
400     size_t lastWriteAddr = req->fruInventoryOffset + writeLen;
401     if (fruCache.size() < lastWriteAddr)
402     {
403         fruCache.resize(req->fruInventoryOffset + writeLen);
404     }
405 
406     std::copy(req->data, req->data + writeLen,
407               fruCache.begin() + req->fruInventoryOffset);
408 
409     bool atEnd = false;
410 
411     if (fruCache.size() >= sizeof(FRUHeader))
412     {
413 
414         FRUHeader* header = reinterpret_cast<FRUHeader*>(fruCache.data());
415 
416         size_t lastRecordStart = std::max(
417             header->internalOffset,
418             std::max(header->chassisOffset,
419                      std::max(header->boardOffset, header->productOffset)));
420         // TODO: Handle Multi-Record FRUs?
421 
422         lastRecordStart *= 8; // header starts in are multiples of 8 bytes
423 
424         // get the length of the area in multiples of 8 bytes
425         if (lastWriteAddr > (lastRecordStart + 1))
426         {
427             // second byte in record area is the length
428             int areaLength(fruCache[lastRecordStart + 1]);
429             areaLength *= 8; // it is in multiples of 8 bytes
430 
431             if (lastWriteAddr >= (areaLength + lastRecordStart))
432             {
433                 atEnd = true;
434             }
435         }
436     }
437     uint8_t* respPtr = static_cast<uint8_t*>(response);
438     if (atEnd)
439     {
440         // cancel timer, we're at the end so might as well send it
441         cacheTimer->stop();
442         if (!writeFru())
443         {
444             return IPMI_CC_INVALID_FIELD_REQUEST;
445         }
446         *respPtr = std::min(fruCache.size(), static_cast<size_t>(0xFF));
447     }
448     else
449     {
450         // start a timer, if no further data is sent in cacheTimeoutSeconds
451         // seconds, check to see if it is valid
452         createTimer();
453         cacheTimer->start(std::chrono::duration_cast<std::chrono::microseconds>(
454             std::chrono::seconds(cacheTimeoutSeconds)));
455         *respPtr = 0;
456     }
457 
458     *dataLen = 1;
459 
460     return IPMI_CC_OK;
461 }
462 
463 ipmi_ret_t getFruSdrCount(size_t& count)
464 {
465     ipmi_ret_t ret = replaceCacheFru(0);
466     if (ret != IPMI_CC_OK)
467     {
468         return ret;
469     }
470     count = deviceHashes.size();
471     return IPMI_CC_OK;
472 }
473 
474 ipmi_ret_t getFruSdrs(size_t index, get_sdr::SensorDataFruRecord& resp)
475 {
476     ipmi_ret_t ret = replaceCacheFru(0); // this will update the hash list
477     if (ret != IPMI_CC_OK)
478     {
479         return ret;
480     }
481     if (deviceHashes.size() < index)
482     {
483         return IPMI_CC_INVALID_FIELD_REQUEST;
484     }
485     auto device = deviceHashes.begin() + index;
486     uint8_t& bus = device->second.first;
487     uint8_t& address = device->second.second;
488 
489     ManagedObjectType frus;
490 
491     sdbusplus::message_t getObjects = dbus.new_method_call(
492         fruDeviceServiceName, "/", "org.freedesktop.DBus.ObjectManager",
493         "GetManagedObjects");
494     try
495     {
496         sdbusplus::message_t resp = dbus.call(getObjects);
497         resp.read(frus);
498     }
499     catch (const sdbusplus::exception_t&)
500     {
501         return IPMI_CC_RESPONSE_ERROR;
502     }
503     boost::container::flat_map<std::string, DbusVariant>* fruData = nullptr;
504     auto fru =
505         std::find_if(frus.begin(), frus.end(),
506                      [bus, address, &fruData](ManagedEntry& entry) {
507                          auto findFruDevice =
508                              entry.second.find("xyz.openbmc_project.FruDevice");
509                          if (findFruDevice == entry.second.end())
510                          {
511                              return false;
512                          }
513                          fruData = &(findFruDevice->second);
514                          auto findBus = findFruDevice->second.find("BUS");
515                          auto findAddress =
516                              findFruDevice->second.find("ADDRESS");
517                          if (findBus == findFruDevice->second.end() ||
518                              findAddress == findFruDevice->second.end())
519                          {
520                              return false;
521                          }
522                          if (std::get<uint32_t>(findBus->second) != bus)
523                          {
524                              return false;
525                          }
526                          if (std::get<uint32_t>(findAddress->second) != address)
527                          {
528                              return false;
529                          }
530                          return true;
531                      });
532     if (fru == frus.end())
533     {
534         return IPMI_CC_RESPONSE_ERROR;
535     }
536     std::string name;
537     auto findProductName = fruData->find("BOARD_PRODUCT_NAME");
538     auto findBoardName = fruData->find("PRODUCT_PRODUCT_NAME");
539     if (findProductName != fruData->end())
540     {
541         name = std::get<std::string>(findProductName->second);
542     }
543     else if (findBoardName != fruData->end())
544     {
545         name = std::get<std::string>(findBoardName->second);
546     }
547     else
548     {
549         name = "UNKNOWN";
550     }
551     if (name.size() > maxFruSdrNameSize)
552     {
553         name = name.substr(0, maxFruSdrNameSize);
554     }
555     size_t sizeDiff = maxFruSdrNameSize - name.size();
556 
557     resp.header.record_id_lsb = 0x0; // calling code is to implement these
558     resp.header.record_id_msb = 0x0;
559     resp.header.sdr_version = ipmiSdrVersion;
560     resp.header.record_type = 0x11; // FRU Device Locator
561     resp.header.record_length = sizeof(resp.body) + sizeof(resp.key) - sizeDiff;
562     resp.key.deviceAddress = 0x20;
563     resp.key.fruID = device->first;
564     resp.key.accessLun = 0x80; // logical / physical fru device
565     resp.key.channelNumber = 0x0;
566     resp.body.reserved = 0x0;
567     resp.body.deviceType = 0x10;
568     resp.body.entityID = 0x0;
569     resp.body.entityInstance = 0x1;
570     resp.body.oem = 0x0;
571     resp.body.deviceIDLen = name.size();
572     name.copy(resp.body.deviceID, name.size());
573 
574     return IPMI_CC_OK;
575 }
576 
577 ipmi_ret_t ipmiStorageReserveSDR(ipmi_netfn_t netfn, ipmi_cmd_t cmd,
578                                  ipmi_request_t, ipmi_response_t response,
579                                  ipmi_data_len_t dataLen, ipmi_context_t)
580 {
581     printCommand(+netfn, +cmd);
582 
583     if (*dataLen)
584     {
585         *dataLen = 0;
586         return IPMI_CC_REQ_DATA_LEN_INVALID;
587     }
588     *dataLen = 0; // default to 0 in case of an error
589     sdrReservationID++;
590     if (sdrReservationID == 0)
591     {
592         sdrReservationID++;
593     }
594     *dataLen = 2;
595     auto resp = static_cast<uint8_t*>(response);
596     resp[0] = sdrReservationID & 0xFF;
597     resp[1] = sdrReservationID >> 8;
598 
599     return IPMI_CC_OK;
600 }
601 
602 ipmi_ret_t ipmiStorageGetSDR(ipmi_netfn_t netfn, ipmi_cmd_t cmd,
603                              ipmi_request_t request, ipmi_response_t response,
604                              ipmi_data_len_t dataLen, ipmi_context_t)
605 {
606     printCommand(+netfn, +cmd);
607 
608     if (*dataLen != 6)
609     {
610         *dataLen = 0;
611         return IPMI_CC_REQ_DATA_LEN_INVALID;
612     }
613     auto requestedSize = *dataLen;
614     *dataLen = 0; // default to 0 in case of an error
615 
616     constexpr uint16_t lastRecordIndex = 0xFFFF;
617     auto req = static_cast<GetSDRReq*>(request);
618 
619     // reservation required for partial reads with non zero offset into
620     // record
621     if ((sdrReservationID == 0 || req->reservationID != sdrReservationID) &&
622         req->offset)
623     {
624         return IPMI_CC_INVALID_RESERVATION_ID;
625     }
626 
627     if (sensorTree.empty() && !getSensorSubtree(sensorTree))
628     {
629         return IPMI_CC_RESPONSE_ERROR;
630     }
631 
632     size_t fruCount = 0;
633     ipmi_ret_t ret = ipmi::storage::getFruSdrCount(fruCount);
634     if (ret != IPMI_CC_OK)
635     {
636         return ret;
637     }
638 
639     size_t lastRecord = sensorTree.size() + fruCount - 1;
640     if (req->recordID == lastRecordIndex)
641     {
642         req->recordID = lastRecord;
643     }
644     if (req->recordID > lastRecord)
645     {
646         return IPMI_CC_INVALID_FIELD_REQUEST;
647     }
648 
649     uint16_t nextRecord = lastRecord > static_cast<size_t>(req->recordID + 1)
650                               ? req->recordID + 1
651                               : 0XFFFF;
652 
653     auto responseClear = static_cast<uint8_t*>(response);
654     std::fill(responseClear, responseClear + requestedSize, 0);
655 
656     auto resp = static_cast<get_sdr::GetSdrResp*>(response);
657     resp->next_record_id_lsb = nextRecord & 0xFF;
658     resp->next_record_id_msb = nextRecord >> 8;
659 
660     if (req->recordID >= sensorTree.size())
661     {
662         size_t fruIndex = req->recordID - sensorTree.size();
663         if (fruIndex >= fruCount)
664         {
665             return IPMI_CC_INVALID_FIELD_REQUEST;
666         }
667         get_sdr::SensorDataFruRecord data;
668         if (req->offset > sizeof(data))
669         {
670             return IPMI_CC_INVALID_FIELD_REQUEST;
671         }
672         ret = ipmi::storage::getFruSdrs(fruIndex, data);
673         if (ret != IPMI_CC_OK)
674         {
675             return ret;
676         }
677         data.header.record_id_msb = req->recordID << 8;
678         data.header.record_id_lsb = req->recordID & 0xFF;
679         if (sizeof(data) < (req->offset + req->bytesToRead))
680         {
681             req->bytesToRead = sizeof(data) - req->offset;
682         }
683         *dataLen = req->bytesToRead + 2; // next record
684         std::memcpy(&resp->record_data, (char*)&data + req->offset,
685                     req->bytesToRead);
686         return IPMI_CC_OK;
687     }
688 
689     std::string connection;
690     std::string path;
691     uint16_t sensorIndex = req->recordID;
692     for (const auto& sensor : sensorTree)
693     {
694         if (sensorIndex-- == 0)
695         {
696             if (!sensor.second.size())
697             {
698                 return IPMI_CC_RESPONSE_ERROR;
699             }
700             connection = sensor.second.begin()->first;
701             path = sensor.first;
702             break;
703         }
704     }
705 
706     SensorMap sensorMap;
707     if (!getSensorMap(connection, path, sensorMap))
708     {
709         return IPMI_CC_RESPONSE_ERROR;
710     }
711     uint8_t sensornumber = (req->recordID & 0xFF);
712     get_sdr::SensorDataFullRecord record = {};
713 
714     record.header.record_id_msb = req->recordID << 8;
715     record.header.record_id_lsb = req->recordID & 0xFF;
716     record.header.sdr_version = ipmiSdrVersion;
717     record.header.record_type = get_sdr::SENSOR_DATA_FULL_RECORD;
718     record.header.record_length = sizeof(get_sdr::SensorDataFullRecord) -
719                                   sizeof(get_sdr::SensorDataRecordHeader);
720     record.key.owner_id = 0x20;
721     record.key.owner_lun = 0x0;
722     record.key.sensor_number = sensornumber;
723 
724     record.body.entity_id = 0x0;
725     record.body.entity_instance = 0x01;
726     record.body.sensor_capabilities = 0x60; // auto rearm - todo hysteresis
727     record.body.sensor_type = getSensorTypeFromPath(path);
728     std::string type = getSensorTypeStringFromPath(path);
729     auto typeCstr = type.c_str();
730     auto findUnits = sensorUnits.find(typeCstr);
731     if (findUnits != sensorUnits.end())
732     {
733         record.body.sensor_units_2_base =
734             static_cast<uint8_t>(findUnits->second);
735     } // else default 0x0 unspecified
736 
737     record.body.event_reading_type = getSensorEventTypeFromPath(path);
738 
739     auto sensorObject = sensorMap.find("xyz.openbmc_project.Sensor.Value");
740     if (sensorObject == sensorMap.end())
741     {
742         return IPMI_CC_RESPONSE_ERROR;
743     }
744 
745     auto maxObject = sensorObject->second.find("MaxValue");
746     auto minObject = sensorObject->second.find("MinValue");
747     double max = 128;
748     double min = -127;
749     if (maxObject != sensorObject->second.end())
750     {
751         max = std::visit(VariantToDoubleVisitor(), maxObject->second);
752     }
753 
754     if (minObject != sensorObject->second.end())
755     {
756         min = std::visit(VariantToDoubleVisitor(), minObject->second);
757     }
758 
759     int16_t mValue;
760     int8_t rExp;
761     int16_t bValue;
762     int8_t bExp;
763     bool bSigned;
764 
765     if (!getSensorAttributes(max, min, mValue, rExp, bValue, bExp, bSigned))
766     {
767         return IPMI_CC_RESPONSE_ERROR;
768     }
769 
770     // apply M, B, and exponents, M and B are 10 bit values, exponents are 4
771     record.body.m_lsb = mValue & 0xFF;
772 
773     // move the smallest bit of the MSB into place (bit 9)
774     // the MSbs are bits 7:8 in m_msb_and_tolerance
775     uint8_t mMsb = (mValue & (1 << 8)) > 0 ? (1 << 6) : 0;
776 
777     // assign the negative
778     if (mValue < 0)
779     {
780         mMsb |= (1 << 7);
781     }
782     record.body.m_msb_and_tolerance = mMsb;
783 
784     record.body.b_lsb = bValue & 0xFF;
785 
786     // move the smallest bit of the MSB into place
787     // the MSbs are bits 7:8 in b_msb_and_accuracy_lsb
788     uint8_t bMsb = (bValue & (1 << 8)) > 0 ? (1 << 6) : 0;
789 
790     // assign the negative
791     if (bValue < 0)
792     {
793         bMsb |= (1 << 7);
794     }
795     record.body.b_msb_and_accuracy_lsb = bMsb;
796 
797     record.body.r_b_exponents = bExp & 0x7;
798     if (bExp < 0)
799     {
800         record.body.r_b_exponents |= 1 << 3;
801     }
802     record.body.r_b_exponents = (rExp & 0x7) << 4;
803     if (rExp < 0)
804     {
805         record.body.r_b_exponents |= 1 << 7;
806     }
807 
808     // todo fill out rest of units
809     if (bSigned)
810     {
811         record.body.sensor_units_1 = 1 << 7;
812     }
813 
814     // populate sensor name from path
815     std::string name;
816     size_t nameStart = path.rfind("/");
817     if (nameStart != std::string::npos)
818     {
819         name = path.substr(nameStart + 1, std::string::npos - nameStart);
820     }
821 
822     std::replace(name.begin(), name.end(), '_', ' ');
823     if (name.size() > FULL_RECORD_ID_STR_MAX_LENGTH)
824     {
825         name.resize(FULL_RECORD_ID_STR_MAX_LENGTH);
826     }
827     record.body.id_string_info = name.size();
828     std::strncpy(record.body.id_string, name.c_str(),
829                  sizeof(record.body.id_string));
830 
831     if (sizeof(get_sdr::SensorDataFullRecord) <
832         (req->offset + req->bytesToRead))
833     {
834         req->bytesToRead = sizeof(get_sdr::SensorDataFullRecord) - req->offset;
835     }
836 
837     *dataLen =
838         2 + req->bytesToRead; // bytesToRead + MSB and LSB of next record id
839 
840     std::memcpy(&resp->record_data, (char*)&record + req->offset,
841                 req->bytesToRead);
842 
843     return IPMI_CC_OK;
844 }
845 
846 static int getSensorConnectionByName(std::string& name, std::string& connection,
847                                      std::string& path)
848 {
849     if (sensorTree.empty() && !getSensorSubtree(sensorTree))
850     {
851         return -1;
852     }
853 
854     for (const auto& sensor : sensorTree)
855     {
856         path = sensor.first;
857         if (path.find(name) != std::string::npos)
858         {
859             connection = sensor.second.begin()->first;
860             return 0;
861         }
862     }
863     return -1;
864 }
865 
866 int getSensorThreshold(std::string& name, std::string& thresholdStr)
867 {
868     std::string connection;
869     std::string path;
870     int ret = -1;
871     thresholdStr = "";
872 
873     ret = getSensorConnectionByName(name, connection, path);
874     if (ret < 0)
875     {
876         return ret;
877     }
878 
879     SensorMap sensorMap;
880     if (!getSensorMap(connection, path, sensorMap))
881     {
882         return ret;
883     }
884 
885     // Iterate threshold interfaces with priority order
886     for (auto& interface : thresholdCheckedOrder)
887     {
888         auto interfaceProperty = alarmProperties.find(interface);
889         if (interfaceProperty == alarmProperties.end())
890         {
891             continue;
892         }
893 
894         auto propertyValue = interfaceProperty->second;
895 
896         // Checks threshold properties value in sensorMap
897         auto thresholdInterfaceSensorMap = sensorMap.find(interface);
898 
899         // Ignore if interface not set
900         if (thresholdInterfaceSensorMap == sensorMap.end())
901         {
902             continue;
903         }
904 
905         auto& thresholdMap = thresholdInterfaceSensorMap->second;
906 
907         auto& propertyAlarmHigh = propertyValue.at(AlarmType::high);
908         auto alarmHigh = thresholdMap.find(propertyAlarmHigh.name);
909         if (alarmHigh != thresholdMap.end())
910         {
911             if (std::get<bool>(alarmHigh->second))
912             {
913                 thresholdStr = propertyAlarmHigh.threshold;
914                 break;
915             }
916         }
917 
918         auto& propertyAlarmLow = propertyValue.at(AlarmType::low);
919         auto alarmLow = thresholdMap.find(propertyAlarmLow.name);
920         if (alarmLow != thresholdMap.end())
921         {
922             if (std::get<bool>(alarmLow->second))
923             {
924                 thresholdStr = propertyAlarmLow.threshold;
925                 break;
926             }
927         }
928     }
929 
930     return 0;
931 }
932 
933 int getSensorValue(std::string& name, double& val)
934 {
935     std::string connection;
936     std::string path;
937     int ret = -1;
938 
939     ret = getSensorConnectionByName(name, connection, path);
940     if (ret < 0)
941     {
942         return ret;
943     }
944 
945     SensorMap sensorMap;
946     if (!getSensorMap(connection, path, sensorMap))
947     {
948         return ret;
949     }
950     auto sensorObject = sensorMap.find("xyz.openbmc_project.Sensor.Value");
951 
952     if (sensorObject == sensorMap.end() ||
953         sensorObject->second.find("Value") == sensorObject->second.end())
954     {
955         return ret;
956     }
957     auto& valueVariant = sensorObject->second["Value"];
958     val = std::visit(VariantToDoubleVisitor(), valueVariant);
959 
960     return 0;
961 }
962 
963 const static boost::container::flat_map<const char*, std::string, CmpStr>
964     sensorUnitStr{{{"temperature", "C"},
965                    {"voltage", "V"},
966                    {"current", "mA"},
967                    {"fan_tach", "RPM"},
968                    {"fan_pwm", "RPM"},
969                    {"power", "W"}}};
970 
971 int getSensorUnit(std::string& name, std::string& unit)
972 {
973     std::string connection;
974     std::string path;
975     int ret = -1;
976 
977     ret = getSensorConnectionByName(name, connection, path);
978     if (ret < 0)
979     {
980         return ret;
981     }
982 
983     std::string sensorTypeStr = getSensorTypeStringFromPath(path);
984     auto findSensor = sensorUnitStr.find(sensorTypeStr.c_str());
985     if (findSensor != sensorUnitStr.end())
986     {
987         unit = findSensor->second;
988         return 0;
989     }
990     else
991         return -1;
992 }
993 
994 ipmi_ret_t ipmiStorageGetFRUInvAreaInfo(ipmi_netfn_t, ipmi_cmd_t,
995                                         ipmi_request_t request,
996                                         ipmi_response_t response,
997                                         ipmi_data_len_t dataLen, ipmi_context_t)
998 {
999     if (*dataLen != 1)
1000     {
1001         *dataLen = 0;
1002         return IPMI_CC_REQ_DATA_LEN_INVALID;
1003     }
1004     *dataLen = 0; // default to 0 in case of an error
1005 
1006     uint8_t reqDev = *(static_cast<uint8_t*>(request));
1007     if (reqDev == 0xFF)
1008     {
1009         return IPMI_CC_INVALID_FIELD_REQUEST;
1010     }
1011     ipmi_ret_t status = replaceCacheFru(reqDev);
1012 
1013     if (status != IPMI_CC_OK)
1014     {
1015         return status;
1016     }
1017 
1018     GetFRUAreaResp* respPtr = static_cast<GetFRUAreaResp*>(response);
1019     respPtr->inventorySizeLSB = fruCache.size() & 0xFF;
1020     respPtr->inventorySizeMSB = fruCache.size() >> 8;
1021     respPtr->accessType = static_cast<uint8_t>(GetFRUAreaAccessType::byte);
1022 
1023     *dataLen = sizeof(GetFRUAreaResp);
1024     return IPMI_CC_OK;
1025 }
1026 
1027 void registerStorageFunctions()
1028 {
1029     // <Get FRU Inventory Area Info>
1030     ipmiPrintAndRegister(
1031         NETFUN_STORAGE,
1032         static_cast<ipmi_cmd_t>(IPMINetfnStorageCmds::ipmiCmdGetFRUInvAreaInfo),
1033         NULL, ipmiStorageGetFRUInvAreaInfo, PRIVILEGE_OPERATOR);
1034 
1035     // <READ FRU Data>
1036     ipmiPrintAndRegister(
1037         NETFUN_STORAGE,
1038         static_cast<ipmi_cmd_t>(IPMINetfnStorageCmds::ipmiCmdReadFRUData), NULL,
1039         ipmiStorageReadFRUData, PRIVILEGE_OPERATOR);
1040 
1041     // <WRITE FRU Data>
1042     ipmiPrintAndRegister(
1043         NETFUN_STORAGE,
1044         static_cast<ipmi_cmd_t>(IPMINetfnStorageCmds::ipmiCmdWriteFRUData),
1045         NULL, ipmiStorageWriteFRUData, PRIVILEGE_OPERATOR);
1046 
1047     // <Reserve SDR Repo>
1048     ipmiPrintAndRegister(
1049         NETFUN_STORAGE,
1050         static_cast<ipmi_cmd_t>(IPMINetfnStorageCmds::ipmiCmdReserveSDR),
1051         nullptr, ipmiStorageReserveSDR, PRIVILEGE_USER);
1052 
1053     // <Get Sdr>
1054     ipmiPrintAndRegister(
1055         NETFUN_STORAGE,
1056         static_cast<ipmi_cmd_t>(IPMINetfnStorageCmds::ipmiCmdGetSDR), nullptr,
1057         ipmiStorageGetSDR, PRIVILEGE_USER);
1058     return;
1059 }
1060 } // namespace storage
1061 } // namespace ipmi
1062