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