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<sdbusplus::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 
getSensorMap(std::string sensorConnection,std::string sensorPath,SensorMap & sensorMap)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 
writeFru()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 
createTimer()197 void createTimer()
198 {
199     if (cacheTimer == nullptr)
200     {
201         cacheTimer = std::make_unique<sdbusplus::Timer>(writeFru);
202     }
203 }
204 
replaceCacheFru(uint8_t devId)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 
ipmiStorageReadFRUData(ipmi_netfn_t,ipmi_cmd_t,ipmi_request_t request,ipmi_response_t response,ipmi_data_len_t dataLen,ipmi_context_t)307 ipmi_ret_t ipmiStorageReadFRUData(
308     ipmi_netfn_t, ipmi_cmd_t, ipmi_request_t request, ipmi_response_t response,
309     ipmi_data_len_t dataLen, ipmi_context_t)
310 {
311     if (*dataLen != 4)
312     {
313         *dataLen = 0;
314         return IPMI_CC_REQ_DATA_LEN_INVALID;
315     }
316     *dataLen = 0; // default to 0 in case of an error
317 
318     auto req = static_cast<GetFRUAreaReq*>(request);
319 
320     if (req->countToRead > maxMessageSize - 1)
321     {
322         return IPMI_CC_INVALID_FIELD_REQUEST;
323     }
324     ipmi_ret_t status = replaceCacheFru(req->fruDeviceID);
325 
326     if (status != IPMI_CC_OK)
327     {
328         return status;
329     }
330 
331     size_t fromFRUByteLen = 0;
332     if (req->countToRead + req->fruInventoryOffset < fruCache.size())
333     {
334         fromFRUByteLen = req->countToRead;
335     }
336     else if (fruCache.size() > req->fruInventoryOffset)
337     {
338         fromFRUByteLen = fruCache.size() - req->fruInventoryOffset;
339     }
340     size_t padByteLen = req->countToRead - fromFRUByteLen;
341     uint8_t* respPtr = static_cast<uint8_t*>(response);
342     *respPtr = req->countToRead;
343     std::copy(fruCache.begin() + req->fruInventoryOffset,
344               fruCache.begin() + req->fruInventoryOffset + fromFRUByteLen,
345               ++respPtr);
346     // if longer than the fru is requested, fill with 0xFF
347     if (padByteLen)
348     {
349         respPtr += fromFRUByteLen;
350         std::fill(respPtr, respPtr + padByteLen, 0xFF);
351     }
352     *dataLen = fromFRUByteLen + 1;
353 
354     return IPMI_CC_OK;
355 }
356 
ipmiStorageWriteFRUData(ipmi_netfn_t,ipmi_cmd_t,ipmi_request_t request,ipmi_response_t response,ipmi_data_len_t dataLen,ipmi_context_t)357 ipmi_ret_t ipmiStorageWriteFRUData(
358     ipmi_netfn_t, ipmi_cmd_t, ipmi_request_t request, ipmi_response_t response,
359     ipmi_data_len_t dataLen, ipmi_context_t)
360 {
361     if (*dataLen < 4 ||
362         *dataLen >=
363             0xFF + 3) // count written return is one byte, so limit to one
364                       // byte of data after the three request data bytes
365     {
366         *dataLen = 0;
367         return IPMI_CC_REQ_DATA_LEN_INVALID;
368     }
369 
370     auto req = static_cast<WriteFRUDataReq*>(request);
371     size_t writeLen = *dataLen - 3;
372     *dataLen = 0; // default to 0 in case of an error
373 
374     ipmi_ret_t status = replaceCacheFru(req->fruDeviceID);
375     if (status != IPMI_CC_OK)
376     {
377         return status;
378     }
379     size_t lastWriteAddr = req->fruInventoryOffset + writeLen;
380     if (fruCache.size() < lastWriteAddr)
381     {
382         fruCache.resize(req->fruInventoryOffset + writeLen);
383     }
384 
385     std::copy(req->data, req->data + writeLen,
386               fruCache.begin() + req->fruInventoryOffset);
387 
388     bool atEnd = false;
389 
390     if (fruCache.size() >= sizeof(FRUHeader))
391     {
392         FRUHeader* header = reinterpret_cast<FRUHeader*>(fruCache.data());
393 
394         size_t lastRecordStart = std::max(
395             header->internalOffset,
396             std::max(header->chassisOffset,
397                      std::max(header->boardOffset, header->productOffset)));
398         // TODO: Handle Multi-Record FRUs?
399 
400         lastRecordStart *= 8; // header starts in are multiples of 8 bytes
401 
402         // get the length of the area in multiples of 8 bytes
403         if (lastWriteAddr > (lastRecordStart + 1))
404         {
405             // second byte in record area is the length
406             int areaLength(fruCache[lastRecordStart + 1]);
407             areaLength *= 8; // it is in multiples of 8 bytes
408 
409             if (lastWriteAddr >= (areaLength + lastRecordStart))
410             {
411                 atEnd = true;
412             }
413         }
414     }
415     uint8_t* respPtr = static_cast<uint8_t*>(response);
416     if (atEnd)
417     {
418         // cancel timer, we're at the end so might as well send it
419         cacheTimer->stop();
420         if (!writeFru())
421         {
422             return IPMI_CC_INVALID_FIELD_REQUEST;
423         }
424         *respPtr = std::min(fruCache.size(), static_cast<size_t>(0xFF));
425     }
426     else
427     {
428         // start a timer, if no further data is sent in cacheTimeoutSeconds
429         // seconds, check to see if it is valid
430         createTimer();
431         cacheTimer->start(std::chrono::duration_cast<std::chrono::microseconds>(
432             std::chrono::seconds(cacheTimeoutSeconds)));
433         *respPtr = 0;
434     }
435 
436     *dataLen = 1;
437 
438     return IPMI_CC_OK;
439 }
440 
getFruSdrCount(size_t & count)441 ipmi_ret_t getFruSdrCount(size_t& count)
442 {
443     ipmi_ret_t ret = replaceCacheFru(0);
444     if (ret != IPMI_CC_OK)
445     {
446         return ret;
447     }
448     count = deviceHashes.size();
449     return IPMI_CC_OK;
450 }
451 
getFruSdrs(size_t index,get_sdr::SensorDataFruRecord & resp)452 ipmi_ret_t getFruSdrs(size_t index, get_sdr::SensorDataFruRecord& resp)
453 {
454     ipmi_ret_t ret = replaceCacheFru(0); // this will update the hash list
455     if (ret != IPMI_CC_OK)
456     {
457         return ret;
458     }
459     if (deviceHashes.size() < index)
460     {
461         return IPMI_CC_INVALID_FIELD_REQUEST;
462     }
463     auto device = deviceHashes.begin() + index;
464     uint8_t& bus = device->second.first;
465     uint8_t& address = device->second.second;
466 
467     ManagedObjectType frus;
468 
469     sdbusplus::message_t getObjects = dbus.new_method_call(
470         fruDeviceServiceName, "/", "org.freedesktop.DBus.ObjectManager",
471         "GetManagedObjects");
472     try
473     {
474         sdbusplus::message_t resp = dbus.call(getObjects);
475         resp.read(frus);
476     }
477     catch (const sdbusplus::exception_t&)
478     {
479         return IPMI_CC_RESPONSE_ERROR;
480     }
481     boost::container::flat_map<std::string, DbusVariant>* fruData = nullptr;
482     auto fru = std::find_if(
483         frus.begin(), frus.end(),
484         [bus, address, &fruData](ManagedEntry& entry) {
485             auto findFruDevice =
486                 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 
ipmiStorageReserveSDR(ipmi_netfn_t netfn,ipmi_cmd_t cmd,ipmi_request_t,ipmi_response_t response,ipmi_data_len_t dataLen,ipmi_context_t)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 
ipmiStorageGetSDR(ipmi_netfn_t netfn,ipmi_cmd_t cmd,ipmi_request_t request,ipmi_response_t response,ipmi_data_len_t dataLen,ipmi_context_t)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 (!getSensorSubtree(sensorTree) && sensorTree.empty())
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 
getSensorConnectionByName(std::string & name,std::string & connection,std::string & path)823 static int getSensorConnectionByName(std::string& name, std::string& connection,
824                                      std::string& path)
825 {
826     if (!getSensorSubtree(sensorTree) && sensorTree.empty())
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 
getSensorThreshold(std::string & name,std::string & thresholdStr)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 
getSensorValue(std::string & name,double & val)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 
getSensorUnit(std::string & name,std::string & unit)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 
ipmiStorageGetFRUInvAreaInfo(ipmi_netfn_t,ipmi_cmd_t,ipmi_request_t request,ipmi_response_t response,ipmi_data_len_t dataLen,ipmi_context_t)971 ipmi_ret_t ipmiStorageGetFRUInvAreaInfo(
972     ipmi_netfn_t, ipmi_cmd_t, ipmi_request_t request, ipmi_response_t response,
973     ipmi_data_len_t dataLen, ipmi_context_t)
974 {
975     if (*dataLen != 1)
976     {
977         *dataLen = 0;
978         return IPMI_CC_REQ_DATA_LEN_INVALID;
979     }
980     *dataLen = 0; // default to 0 in case of an error
981 
982     uint8_t reqDev = *(static_cast<uint8_t*>(request));
983     if (reqDev == 0xFF)
984     {
985         return IPMI_CC_INVALID_FIELD_REQUEST;
986     }
987     ipmi_ret_t status = replaceCacheFru(reqDev);
988 
989     if (status != IPMI_CC_OK)
990     {
991         return status;
992     }
993 
994     GetFRUAreaResp* respPtr = static_cast<GetFRUAreaResp*>(response);
995     respPtr->inventorySizeLSB = fruCache.size() & 0xFF;
996     respPtr->inventorySizeMSB = fruCache.size() >> 8;
997     respPtr->accessType = static_cast<uint8_t>(GetFRUAreaAccessType::byte);
998 
999     *dataLen = sizeof(GetFRUAreaResp);
1000     return IPMI_CC_OK;
1001 }
1002 
registerStorageFunctions()1003 void registerStorageFunctions()
1004 {
1005     // <Get FRU Inventory Area Info>
1006     ipmiPrintAndRegister(
1007         NETFUN_STORAGE,
1008         static_cast<ipmi_cmd_t>(IPMINetfnStorageCmds::ipmiCmdGetFRUInvAreaInfo),
1009         NULL, ipmiStorageGetFRUInvAreaInfo, PRIVILEGE_OPERATOR);
1010 
1011     // <READ FRU Data>
1012     ipmiPrintAndRegister(
1013         NETFUN_STORAGE,
1014         static_cast<ipmi_cmd_t>(IPMINetfnStorageCmds::ipmiCmdReadFRUData), NULL,
1015         ipmiStorageReadFRUData, PRIVILEGE_OPERATOR);
1016 
1017     // <WRITE FRU Data>
1018     ipmiPrintAndRegister(
1019         NETFUN_STORAGE,
1020         static_cast<ipmi_cmd_t>(IPMINetfnStorageCmds::ipmiCmdWriteFRUData),
1021         NULL, ipmiStorageWriteFRUData, PRIVILEGE_OPERATOR);
1022 
1023     // <Reserve SDR Repo>
1024     ipmiPrintAndRegister(
1025         NETFUN_STORAGE,
1026         static_cast<ipmi_cmd_t>(IPMINetfnStorageCmds::ipmiCmdReserveSDR),
1027         nullptr, ipmiStorageReserveSDR, PRIVILEGE_USER);
1028 
1029     // <Get Sdr>
1030     ipmiPrintAndRegister(
1031         NETFUN_STORAGE,
1032         static_cast<ipmi_cmd_t>(IPMINetfnStorageCmds::ipmiCmdGetSDR), nullptr,
1033         ipmiStorageGetSDR, PRIVILEGE_USER);
1034     return;
1035 }
1036 } // namespace storage
1037 } // namespace ipmi
1038