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 
29 #include <iostream>
30 #include <unordered_map>
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 uint16_t cacheBus = 0xFFFF;
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     uint8_t fruHash = 0;
240     uint8_t mbFruBus = 0, mbFruAddr = 0;
241 
242     auto device = getMbFruDevice();
243     if (device)
244     {
245         std::tie(mbFruBus, mbFruAddr) = *device;
246         deviceHashes.emplace(0, std::make_pair(mbFruBus, mbFruAddr));
247         fruHash++;
248     }
249 
250     for (const auto& fru : frus)
251     {
252         auto fruIface = fru.second.find("xyz.openbmc_project.FruDevice");
253         if (fruIface == fru.second.end())
254         {
255             continue;
256         }
257 
258         auto busFind = fruIface->second.find("BUS");
259         auto addrFind = fruIface->second.find("ADDRESS");
260         if (busFind == fruIface->second.end() ||
261             addrFind == fruIface->second.end())
262         {
263             phosphor::logging::log<phosphor::logging::level::INFO>(
264                 "fru device missing Bus or Address",
265                 phosphor::logging::entry("FRU=%s", fru.first.str.c_str()));
266             continue;
267         }
268 
269         uint8_t fruBus = std::get<uint32_t>(busFind->second);
270         uint8_t fruAddr = std::get<uint32_t>(addrFind->second);
271         if (fruBus != mbFruBus || fruAddr != mbFruAddr)
272         {
273             deviceHashes.emplace(fruHash, std::make_pair(fruBus, fruAddr));
274             fruHash++;
275         }
276     }
277     auto deviceFind = deviceHashes.find(devId);
278     if (deviceFind == deviceHashes.end())
279     {
280         return IPMI_CC_SENSOR_INVALID;
281     }
282 
283     fruCache.clear();
284     sdbusplus::message_t getRawFru = dbus.new_method_call(
285         fruDeviceServiceName, "/xyz/openbmc_project/FruDevice",
286         "xyz.openbmc_project.FruDeviceManager", "GetRawFru");
287     cacheBus = deviceFind->second.first;
288     cacheAddr = deviceFind->second.second;
289     getRawFru.append(cacheBus, cacheAddr);
290     try
291     {
292         sdbusplus::message_t getRawResp = dbus.call(getRawFru);
293         getRawResp.read(fruCache);
294     }
295     catch (const sdbusplus::exception_t&)
296     {
297         lastDevId = 0xFF;
298         cacheBus = 0xFFFF;
299         cacheAddr = 0xFF;
300         return IPMI_CC_RESPONSE_ERROR;
301     }
302 
303     lastDevId = devId;
304     return IPMI_CC_OK;
305 }
306 
307 ipmi_ret_t ipmiStorageReadFRUData(ipmi_netfn_t, ipmi_cmd_t,
308                                   ipmi_request_t request,
309                                   ipmi_response_t response,
310                                   ipmi_data_len_t dataLen, ipmi_context_t)
311 {
312     if (*dataLen != 4)
313     {
314         *dataLen = 0;
315         return IPMI_CC_REQ_DATA_LEN_INVALID;
316     }
317     *dataLen = 0; // default to 0 in case of an error
318 
319     auto req = static_cast<GetFRUAreaReq*>(request);
320 
321     if (req->countToRead > maxMessageSize - 1)
322     {
323         return IPMI_CC_INVALID_FIELD_REQUEST;
324     }
325     ipmi_ret_t status = replaceCacheFru(req->fruDeviceID);
326 
327     if (status != IPMI_CC_OK)
328     {
329         return status;
330     }
331 
332     size_t fromFRUByteLen = 0;
333     if (req->countToRead + req->fruInventoryOffset < fruCache.size())
334     {
335         fromFRUByteLen = req->countToRead;
336     }
337     else if (fruCache.size() > req->fruInventoryOffset)
338     {
339         fromFRUByteLen = fruCache.size() - req->fruInventoryOffset;
340     }
341     size_t padByteLen = req->countToRead - fromFRUByteLen;
342     uint8_t* respPtr = static_cast<uint8_t*>(response);
343     *respPtr = req->countToRead;
344     std::copy(fruCache.begin() + req->fruInventoryOffset,
345               fruCache.begin() + req->fruInventoryOffset + fromFRUByteLen,
346               ++respPtr);
347     // if longer than the fru is requested, fill with 0xFF
348     if (padByteLen)
349     {
350         respPtr += fromFRUByteLen;
351         std::fill(respPtr, respPtr + padByteLen, 0xFF);
352     }
353     *dataLen = fromFRUByteLen + 1;
354 
355     return IPMI_CC_OK;
356 }
357 
358 ipmi_ret_t ipmiStorageWriteFRUData(ipmi_netfn_t, ipmi_cmd_t,
359                                    ipmi_request_t request,
360                                    ipmi_response_t response,
361                                    ipmi_data_len_t dataLen, ipmi_context_t)
362 {
363     if (*dataLen < 4 ||
364         *dataLen >=
365             0xFF + 3) // count written return is one byte, so limit to one
366                       // byte of data after the three request data bytes
367     {
368         *dataLen = 0;
369         return IPMI_CC_REQ_DATA_LEN_INVALID;
370     }
371 
372     auto req = static_cast<WriteFRUDataReq*>(request);
373     size_t writeLen = *dataLen - 3;
374     *dataLen = 0; // default to 0 in case of an error
375 
376     ipmi_ret_t status = replaceCacheFru(req->fruDeviceID);
377     if (status != IPMI_CC_OK)
378     {
379         return status;
380     }
381     size_t lastWriteAddr = req->fruInventoryOffset + writeLen;
382     if (fruCache.size() < lastWriteAddr)
383     {
384         fruCache.resize(req->fruInventoryOffset + writeLen);
385     }
386 
387     std::copy(req->data, req->data + writeLen,
388               fruCache.begin() + req->fruInventoryOffset);
389 
390     bool atEnd = false;
391 
392     if (fruCache.size() >= sizeof(FRUHeader))
393     {
394         FRUHeader* header = reinterpret_cast<FRUHeader*>(fruCache.data());
395 
396         size_t lastRecordStart = std::max(
397             header->internalOffset,
398             std::max(header->chassisOffset,
399                      std::max(header->boardOffset, header->productOffset)));
400         // TODO: Handle Multi-Record FRUs?
401 
402         lastRecordStart *= 8; // header starts in are multiples of 8 bytes
403 
404         // get the length of the area in multiples of 8 bytes
405         if (lastWriteAddr > (lastRecordStart + 1))
406         {
407             // second byte in record area is the length
408             int areaLength(fruCache[lastRecordStart + 1]);
409             areaLength *= 8; // it is in multiples of 8 bytes
410 
411             if (lastWriteAddr >= (areaLength + lastRecordStart))
412             {
413                 atEnd = true;
414             }
415         }
416     }
417     uint8_t* respPtr = static_cast<uint8_t*>(response);
418     if (atEnd)
419     {
420         // cancel timer, we're at the end so might as well send it
421         cacheTimer->stop();
422         if (!writeFru())
423         {
424             return IPMI_CC_INVALID_FIELD_REQUEST;
425         }
426         *respPtr = std::min(fruCache.size(), static_cast<size_t>(0xFF));
427     }
428     else
429     {
430         // start a timer, if no further data is sent in cacheTimeoutSeconds
431         // seconds, check to see if it is valid
432         createTimer();
433         cacheTimer->start(std::chrono::duration_cast<std::chrono::microseconds>(
434             std::chrono::seconds(cacheTimeoutSeconds)));
435         *respPtr = 0;
436     }
437 
438     *dataLen = 1;
439 
440     return IPMI_CC_OK;
441 }
442 
443 ipmi_ret_t getFruSdrCount(size_t& count)
444 {
445     ipmi_ret_t ret = replaceCacheFru(0);
446     if (ret != IPMI_CC_OK)
447     {
448         return ret;
449     }
450     count = deviceHashes.size();
451     return IPMI_CC_OK;
452 }
453 
454 ipmi_ret_t getFruSdrs(size_t index, get_sdr::SensorDataFruRecord& resp)
455 {
456     ipmi_ret_t ret = replaceCacheFru(0); // this will update the hash list
457     if (ret != IPMI_CC_OK)
458     {
459         return ret;
460     }
461     if (deviceHashes.size() < index)
462     {
463         return IPMI_CC_INVALID_FIELD_REQUEST;
464     }
465     auto device = deviceHashes.begin() + index;
466     uint8_t& bus = device->second.first;
467     uint8_t& address = device->second.second;
468 
469     ManagedObjectType frus;
470 
471     sdbusplus::message_t getObjects = dbus.new_method_call(
472         fruDeviceServiceName, "/", "org.freedesktop.DBus.ObjectManager",
473         "GetManagedObjects");
474     try
475     {
476         sdbusplus::message_t resp = dbus.call(getObjects);
477         resp.read(frus);
478     }
479     catch (const sdbusplus::exception_t&)
480     {
481         return IPMI_CC_RESPONSE_ERROR;
482     }
483     boost::container::flat_map<std::string, DbusVariant>* fruData = nullptr;
484     auto fru = std::find_if(frus.begin(), frus.end(),
485                             [bus, address, &fruData](ManagedEntry& entry) {
486         auto findFruDevice = entry.second.find("xyz.openbmc_project.FruDevice");
487         if (findFruDevice == entry.second.end())
488         {
489             return false;
490         }
491         fruData = &(findFruDevice->second);
492         auto findBus = findFruDevice->second.find("BUS");
493         auto findAddress = findFruDevice->second.find("ADDRESS");
494         if (findBus == findFruDevice->second.end() ||
495             findAddress == findFruDevice->second.end())
496         {
497             return false;
498         }
499         if (std::get<uint32_t>(findBus->second) != bus)
500         {
501             return false;
502         }
503         if (std::get<uint32_t>(findAddress->second) != address)
504         {
505             return false;
506         }
507         return true;
508     });
509     if (fru == frus.end())
510     {
511         return IPMI_CC_RESPONSE_ERROR;
512     }
513     std::string name;
514     auto findProductName = fruData->find("BOARD_PRODUCT_NAME");
515     auto findBoardName = fruData->find("PRODUCT_PRODUCT_NAME");
516     if (findProductName != fruData->end())
517     {
518         name = std::get<std::string>(findProductName->second);
519     }
520     else if (findBoardName != fruData->end())
521     {
522         name = std::get<std::string>(findBoardName->second);
523     }
524     else
525     {
526         name = "UNKNOWN";
527     }
528     if (name.size() > maxFruSdrNameSize)
529     {
530         name = name.substr(0, maxFruSdrNameSize);
531     }
532     size_t sizeDiff = maxFruSdrNameSize - name.size();
533 
534     resp.header.record_id_lsb = 0x0; // calling code is to implement these
535     resp.header.record_id_msb = 0x0;
536     resp.header.sdr_version = ipmiSdrVersion;
537     resp.header.record_type = 0x11; // FRU Device Locator
538     resp.header.record_length = sizeof(resp.body) + sizeof(resp.key) - sizeDiff;
539     resp.key.deviceAddress = 0x20;
540     resp.key.fruID = device->first;
541     resp.key.accessLun = 0x80; // logical / physical fru device
542     resp.key.channelNumber = 0x0;
543     resp.body.reserved = 0x0;
544     resp.body.deviceType = 0x10;
545     resp.body.entityID = 0x0;
546     resp.body.entityInstance = 0x1;
547     resp.body.oem = 0x0;
548     resp.body.deviceIDLen = name.size();
549     name.copy(resp.body.deviceID, name.size());
550 
551     return IPMI_CC_OK;
552 }
553 
554 ipmi_ret_t ipmiStorageReserveSDR(ipmi_netfn_t netfn, ipmi_cmd_t cmd,
555                                  ipmi_request_t, ipmi_response_t response,
556                                  ipmi_data_len_t dataLen, ipmi_context_t)
557 {
558     printCommand(+netfn, +cmd);
559 
560     if (*dataLen)
561     {
562         *dataLen = 0;
563         return IPMI_CC_REQ_DATA_LEN_INVALID;
564     }
565     *dataLen = 0; // default to 0 in case of an error
566     sdrReservationID++;
567     if (sdrReservationID == 0)
568     {
569         sdrReservationID++;
570     }
571     *dataLen = 2;
572     auto resp = static_cast<uint8_t*>(response);
573     resp[0] = sdrReservationID & 0xFF;
574     resp[1] = sdrReservationID >> 8;
575 
576     return IPMI_CC_OK;
577 }
578 
579 ipmi_ret_t ipmiStorageGetSDR(ipmi_netfn_t netfn, ipmi_cmd_t cmd,
580                              ipmi_request_t request, ipmi_response_t response,
581                              ipmi_data_len_t dataLen, ipmi_context_t)
582 {
583     printCommand(+netfn, +cmd);
584 
585     if (*dataLen != 6)
586     {
587         *dataLen = 0;
588         return IPMI_CC_REQ_DATA_LEN_INVALID;
589     }
590     auto requestedSize = *dataLen;
591     *dataLen = 0; // default to 0 in case of an error
592 
593     constexpr uint16_t lastRecordIndex = 0xFFFF;
594     auto req = static_cast<GetSDRReq*>(request);
595 
596     // reservation required for partial reads with non zero offset into
597     // record
598     if ((sdrReservationID == 0 || req->reservationID != sdrReservationID) &&
599         req->offset)
600     {
601         return IPMI_CC_INVALID_RESERVATION_ID;
602     }
603 
604     if (sensorTree.empty() && !getSensorSubtree(sensorTree))
605     {
606         return IPMI_CC_RESPONSE_ERROR;
607     }
608 
609     size_t fruCount = 0;
610     ipmi_ret_t ret = ipmi::storage::getFruSdrCount(fruCount);
611     if (ret != IPMI_CC_OK)
612     {
613         return ret;
614     }
615 
616     size_t lastRecord = sensorTree.size() + fruCount - 1;
617     if (req->recordID == lastRecordIndex)
618     {
619         req->recordID = lastRecord;
620     }
621     if (req->recordID > lastRecord)
622     {
623         return IPMI_CC_INVALID_FIELD_REQUEST;
624     }
625 
626     uint16_t nextRecord = lastRecord >= static_cast<size_t>(req->recordID + 1)
627                               ? req->recordID + 1
628                               : 0XFFFF;
629 
630     auto responseClear = static_cast<uint8_t*>(response);
631     std::fill(responseClear, responseClear + requestedSize, 0);
632 
633     auto resp = static_cast<get_sdr::GetSdrResp*>(response);
634     resp->next_record_id_lsb = nextRecord & 0xFF;
635     resp->next_record_id_msb = nextRecord >> 8;
636 
637     if (req->recordID >= sensorTree.size())
638     {
639         size_t fruIndex = req->recordID - sensorTree.size();
640         if (fruIndex >= fruCount)
641         {
642             return IPMI_CC_INVALID_FIELD_REQUEST;
643         }
644         get_sdr::SensorDataFruRecord data;
645         if (req->offset > sizeof(data))
646         {
647             return IPMI_CC_INVALID_FIELD_REQUEST;
648         }
649         ret = ipmi::storage::getFruSdrs(fruIndex, data);
650         if (ret != IPMI_CC_OK)
651         {
652             return ret;
653         }
654         data.header.record_id_msb = req->recordID << 8;
655         data.header.record_id_lsb = req->recordID & 0xFF;
656         if (sizeof(data) < (req->offset + req->bytesToRead))
657         {
658             req->bytesToRead = sizeof(data) - req->offset;
659         }
660         *dataLen = req->bytesToRead + 2; // next record
661         std::memcpy(&resp->record_data, (char*)&data + req->offset,
662                     req->bytesToRead);
663         return IPMI_CC_OK;
664     }
665 
666     std::string connection;
667     std::string path;
668     uint16_t sensorIndex = req->recordID;
669     for (const auto& sensor : sensorTree)
670     {
671         if (sensorIndex-- == 0)
672         {
673             if (!sensor.second.size())
674             {
675                 return IPMI_CC_RESPONSE_ERROR;
676             }
677             connection = sensor.second.begin()->first;
678             path = sensor.first;
679             break;
680         }
681     }
682 
683     SensorMap sensorMap;
684     if (!getSensorMap(connection, path, sensorMap))
685     {
686         return IPMI_CC_RESPONSE_ERROR;
687     }
688     uint8_t sensornumber = (req->recordID & 0xFF);
689     get_sdr::SensorDataFullRecord record = {};
690 
691     record.header.record_id_msb = req->recordID << 8;
692     record.header.record_id_lsb = req->recordID & 0xFF;
693     record.header.sdr_version = ipmiSdrVersion;
694     record.header.record_type = get_sdr::SENSOR_DATA_FULL_RECORD;
695     record.header.record_length = sizeof(get_sdr::SensorDataFullRecord) -
696                                   sizeof(get_sdr::SensorDataRecordHeader);
697     record.key.owner_id = 0x20;
698     record.key.owner_lun = 0x0;
699     record.key.sensor_number = sensornumber;
700 
701     record.body.entity_id = 0x0;
702     record.body.entity_instance = 0x01;
703     record.body.sensor_capabilities = 0x60; // auto rearm - todo hysteresis
704     record.body.sensor_type = getSensorTypeFromPath(path);
705     std::string type = getSensorTypeStringFromPath(path);
706     auto typeCstr = type.c_str();
707     auto findUnits = sensorUnits.find(typeCstr);
708     if (findUnits != sensorUnits.end())
709     {
710         record.body.sensor_units_2_base =
711             static_cast<uint8_t>(findUnits->second);
712     } // else default 0x0 unspecified
713 
714     record.body.event_reading_type = getSensorEventTypeFromPath(path);
715 
716     auto sensorObject = sensorMap.find("xyz.openbmc_project.Sensor.Value");
717     if (sensorObject == sensorMap.end())
718     {
719         return IPMI_CC_RESPONSE_ERROR;
720     }
721 
722     auto maxObject = sensorObject->second.find("MaxValue");
723     auto minObject = sensorObject->second.find("MinValue");
724     double max = 128;
725     double min = -127;
726     if (maxObject != sensorObject->second.end())
727     {
728         max = std::visit(VariantToDoubleVisitor(), maxObject->second);
729     }
730 
731     if (minObject != sensorObject->second.end())
732     {
733         min = std::visit(VariantToDoubleVisitor(), minObject->second);
734     }
735 
736     int16_t mValue;
737     int8_t rExp;
738     int16_t bValue;
739     int8_t bExp;
740     bool bSigned;
741 
742     if (!getSensorAttributes(max, min, mValue, rExp, bValue, bExp, bSigned))
743     {
744         return IPMI_CC_RESPONSE_ERROR;
745     }
746 
747     // apply M, B, and exponents, M and B are 10 bit values, exponents are 4
748     record.body.m_lsb = mValue & 0xFF;
749 
750     // move the smallest bit of the MSB into place (bit 9)
751     // the MSbs are bits 7:8 in m_msb_and_tolerance
752     uint8_t mMsb = (mValue & (1 << 8)) > 0 ? (1 << 6) : 0;
753 
754     // assign the negative
755     if (mValue < 0)
756     {
757         mMsb |= (1 << 7);
758     }
759     record.body.m_msb_and_tolerance = mMsb;
760 
761     record.body.b_lsb = bValue & 0xFF;
762 
763     // move the smallest bit of the MSB into place
764     // the MSbs are bits 7:8 in b_msb_and_accuracy_lsb
765     uint8_t bMsb = (bValue & (1 << 8)) > 0 ? (1 << 6) : 0;
766 
767     // assign the negative
768     if (bValue < 0)
769     {
770         bMsb |= (1 << 7);
771     }
772     record.body.b_msb_and_accuracy_lsb = bMsb;
773 
774     record.body.r_b_exponents = bExp & 0x7;
775     if (bExp < 0)
776     {
777         record.body.r_b_exponents |= 1 << 3;
778     }
779     record.body.r_b_exponents = (rExp & 0x7) << 4;
780     if (rExp < 0)
781     {
782         record.body.r_b_exponents |= 1 << 7;
783     }
784 
785     // todo fill out rest of units
786     if (bSigned)
787     {
788         record.body.sensor_units_1 = 1 << 7;
789     }
790 
791     // populate sensor name from path
792     std::string name;
793     size_t nameStart = path.rfind("/");
794     if (nameStart != std::string::npos)
795     {
796         name = path.substr(nameStart + 1, std::string::npos - nameStart);
797     }
798 
799     std::replace(name.begin(), name.end(), '_', ' ');
800     if (name.size() > FULL_RECORD_ID_STR_MAX_LENGTH)
801     {
802         name.resize(FULL_RECORD_ID_STR_MAX_LENGTH);
803     }
804     record.body.id_string_info = name.size();
805     std::strncpy(record.body.id_string, name.c_str(),
806                  sizeof(record.body.id_string));
807 
808     if (sizeof(get_sdr::SensorDataFullRecord) <
809         (req->offset + req->bytesToRead))
810     {
811         req->bytesToRead = sizeof(get_sdr::SensorDataFullRecord) - req->offset;
812     }
813 
814     *dataLen = 2 +
815                req->bytesToRead; // bytesToRead + MSB and LSB of next record id
816 
817     std::memcpy(&resp->record_data, (char*)&record + req->offset,
818                 req->bytesToRead);
819 
820     return IPMI_CC_OK;
821 }
822 
823 static int getSensorConnectionByName(std::string& name, std::string& connection,
824                                      std::string& path)
825 {
826     if (sensorTree.empty() && !getSensorSubtree(sensorTree))
827     {
828         return -1;
829     }
830 
831     for (const auto& sensor : sensorTree)
832     {
833         path = sensor.first;
834         if (path.find(name) != std::string::npos)
835         {
836             connection = sensor.second.begin()->first;
837             return 0;
838         }
839     }
840     return -1;
841 }
842 
843 int getSensorThreshold(std::string& name, std::string& thresholdStr)
844 {
845     std::string connection;
846     std::string path;
847     int ret = -1;
848     thresholdStr = "";
849 
850     ret = getSensorConnectionByName(name, connection, path);
851     if (ret < 0)
852     {
853         return ret;
854     }
855 
856     SensorMap sensorMap;
857     if (!getSensorMap(connection, path, sensorMap))
858     {
859         return ret;
860     }
861 
862     // Iterate threshold interfaces with priority order
863     for (auto& interface : thresholdCheckedOrder)
864     {
865         auto interfaceProperty = alarmProperties.find(interface);
866         if (interfaceProperty == alarmProperties.end())
867         {
868             continue;
869         }
870 
871         auto propertyValue = interfaceProperty->second;
872 
873         // Checks threshold properties value in sensorMap
874         auto thresholdInterfaceSensorMap = sensorMap.find(interface);
875 
876         // Ignore if interface not set
877         if (thresholdInterfaceSensorMap == sensorMap.end())
878         {
879             continue;
880         }
881 
882         auto& thresholdMap = thresholdInterfaceSensorMap->second;
883 
884         auto& propertyAlarmHigh = propertyValue.at(AlarmType::high);
885         auto alarmHigh = thresholdMap.find(propertyAlarmHigh.name);
886         if (alarmHigh != thresholdMap.end())
887         {
888             if (std::get<bool>(alarmHigh->second))
889             {
890                 thresholdStr = propertyAlarmHigh.threshold;
891                 break;
892             }
893         }
894 
895         auto& propertyAlarmLow = propertyValue.at(AlarmType::low);
896         auto alarmLow = thresholdMap.find(propertyAlarmLow.name);
897         if (alarmLow != thresholdMap.end())
898         {
899             if (std::get<bool>(alarmLow->second))
900             {
901                 thresholdStr = propertyAlarmLow.threshold;
902                 break;
903             }
904         }
905     }
906 
907     return 0;
908 }
909 
910 int getSensorValue(std::string& name, double& val)
911 {
912     std::string connection;
913     std::string path;
914     int ret = -1;
915 
916     ret = getSensorConnectionByName(name, connection, path);
917     if (ret < 0)
918     {
919         return ret;
920     }
921 
922     SensorMap sensorMap;
923     if (!getSensorMap(connection, path, sensorMap))
924     {
925         return ret;
926     }
927     auto sensorObject = sensorMap.find("xyz.openbmc_project.Sensor.Value");
928 
929     if (sensorObject == sensorMap.end() ||
930         sensorObject->second.find("Value") == sensorObject->second.end())
931     {
932         return ret;
933     }
934     auto& valueVariant = sensorObject->second["Value"];
935     val = std::visit(VariantToDoubleVisitor(), valueVariant);
936 
937     return 0;
938 }
939 
940 const static boost::container::flat_map<const char*, std::string, CmpStr>
941     sensorUnitStr{{{"temperature", "C"},
942                    {"voltage", "V"},
943                    {"current", "mA"},
944                    {"fan_tach", "RPM"},
945                    {"fan_pwm", "RPM"},
946                    {"power", "W"}}};
947 
948 int getSensorUnit(std::string& name, std::string& unit)
949 {
950     std::string connection;
951     std::string path;
952     int ret = -1;
953 
954     ret = getSensorConnectionByName(name, connection, path);
955     if (ret < 0)
956     {
957         return ret;
958     }
959 
960     std::string sensorTypeStr = getSensorTypeStringFromPath(path);
961     auto findSensor = sensorUnitStr.find(sensorTypeStr.c_str());
962     if (findSensor != sensorUnitStr.end())
963     {
964         unit = findSensor->second;
965         return 0;
966     }
967     else
968         return -1;
969 }
970 
971 ipmi_ret_t ipmiStorageGetFRUInvAreaInfo(ipmi_netfn_t, ipmi_cmd_t,
972                                         ipmi_request_t request,
973                                         ipmi_response_t response,
974                                         ipmi_data_len_t dataLen, ipmi_context_t)
975 {
976     if (*dataLen != 1)
977     {
978         *dataLen = 0;
979         return IPMI_CC_REQ_DATA_LEN_INVALID;
980     }
981     *dataLen = 0; // default to 0 in case of an error
982 
983     uint8_t reqDev = *(static_cast<uint8_t*>(request));
984     if (reqDev == 0xFF)
985     {
986         return IPMI_CC_INVALID_FIELD_REQUEST;
987     }
988     ipmi_ret_t status = replaceCacheFru(reqDev);
989 
990     if (status != IPMI_CC_OK)
991     {
992         return status;
993     }
994 
995     GetFRUAreaResp* respPtr = static_cast<GetFRUAreaResp*>(response);
996     respPtr->inventorySizeLSB = fruCache.size() & 0xFF;
997     respPtr->inventorySizeMSB = fruCache.size() >> 8;
998     respPtr->accessType = static_cast<uint8_t>(GetFRUAreaAccessType::byte);
999 
1000     *dataLen = sizeof(GetFRUAreaResp);
1001     return IPMI_CC_OK;
1002 }
1003 
1004 void registerStorageFunctions()
1005 {
1006     // <Get FRU Inventory Area Info>
1007     ipmiPrintAndRegister(
1008         NETFUN_STORAGE,
1009         static_cast<ipmi_cmd_t>(IPMINetfnStorageCmds::ipmiCmdGetFRUInvAreaInfo),
1010         NULL, ipmiStorageGetFRUInvAreaInfo, PRIVILEGE_OPERATOR);
1011 
1012     // <READ FRU Data>
1013     ipmiPrintAndRegister(
1014         NETFUN_STORAGE,
1015         static_cast<ipmi_cmd_t>(IPMINetfnStorageCmds::ipmiCmdReadFRUData), NULL,
1016         ipmiStorageReadFRUData, PRIVILEGE_OPERATOR);
1017 
1018     // <WRITE FRU Data>
1019     ipmiPrintAndRegister(
1020         NETFUN_STORAGE,
1021         static_cast<ipmi_cmd_t>(IPMINetfnStorageCmds::ipmiCmdWriteFRUData),
1022         NULL, ipmiStorageWriteFRUData, PRIVILEGE_OPERATOR);
1023 
1024     // <Reserve SDR Repo>
1025     ipmiPrintAndRegister(
1026         NETFUN_STORAGE,
1027         static_cast<ipmi_cmd_t>(IPMINetfnStorageCmds::ipmiCmdReserveSDR),
1028         nullptr, ipmiStorageReserveSDR, PRIVILEGE_USER);
1029 
1030     // <Get Sdr>
1031     ipmiPrintAndRegister(
1032         NETFUN_STORAGE,
1033         static_cast<ipmi_cmd_t>(IPMINetfnStorageCmds::ipmiCmdGetSDR), nullptr,
1034         ipmiStorageGetSDR, PRIVILEGE_USER);
1035     return;
1036 }
1037 } // namespace storage
1038 } // namespace ipmi
1039