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::ccSuccess; // 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::ccResponseError;
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::ccSensorInvalid;
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::ccResponseError;
301     }
302 
303     lastDevId = devId;
304     return ipmi::ccSuccess;
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::ccReqDataLenInvalid;
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::ccInvalidFieldRequest;
323     }
324     ipmi_ret_t status = replaceCacheFru(req->fruDeviceID);
325 
326     if (status != ipmi::ccSuccess)
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::ccSuccess;
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::ccReqDataLenInvalid;
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::ccSuccess)
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::ccInvalidFieldRequest;
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::ccSuccess;
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::ccSuccess)
445     {
446         return ret;
447     }
448     count = deviceHashes.size();
449     return ipmi::ccSuccess;
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::ccSuccess)
456     {
457         return ret;
458     }
459     if (deviceHashes.size() < index)
460     {
461         return ipmi::ccInvalidFieldRequest;
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::ccResponseError;
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::ccResponseError;
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.deviceTypeModifier = 0x00;
546     resp.body.entityID = 0x0;
547     resp.body.entityInstance = 0x1;
548     resp.body.oem = 0x0;
549     resp.body.deviceIDLen = name.size();
550     name.copy(resp.body.deviceID, name.size());
551 
552     return ipmi::ccSuccess;
553 }
554 
ipmiStorageReserveSDR(ipmi_netfn_t netfn,ipmi_cmd_t cmd,ipmi_request_t,ipmi_response_t response,ipmi_data_len_t dataLen,ipmi_context_t)555 ipmi_ret_t ipmiStorageReserveSDR(ipmi_netfn_t netfn, ipmi_cmd_t cmd,
556                                  ipmi_request_t, ipmi_response_t response,
557                                  ipmi_data_len_t dataLen, ipmi_context_t)
558 {
559     printCommand(+netfn, +cmd);
560 
561     if (*dataLen)
562     {
563         *dataLen = 0;
564         return ipmi::ccReqDataLenInvalid;
565     }
566     *dataLen = 0; // default to 0 in case of an error
567     sdrReservationID++;
568     if (sdrReservationID == 0)
569     {
570         sdrReservationID++;
571     }
572     *dataLen = 2;
573     auto resp = static_cast<uint8_t*>(response);
574     resp[0] = sdrReservationID & 0xFF;
575     resp[1] = sdrReservationID >> 8;
576 
577     return ipmi::ccSuccess;
578 }
579 
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)580 ipmi_ret_t ipmiStorageGetSDR(ipmi_netfn_t netfn, ipmi_cmd_t cmd,
581                              ipmi_request_t request, ipmi_response_t response,
582                              ipmi_data_len_t dataLen, ipmi_context_t)
583 {
584     printCommand(+netfn, +cmd);
585 
586     if (*dataLen != 6)
587     {
588         *dataLen = 0;
589         return ipmi::ccReqDataLenInvalid;
590     }
591     auto requestedSize = *dataLen;
592     *dataLen = 0; // default to 0 in case of an error
593 
594     constexpr uint16_t lastRecordIndex = 0xFFFF;
595     auto req = static_cast<GetSDRReq*>(request);
596 
597     // reservation required for partial reads with non zero offset into
598     // record
599     if ((sdrReservationID == 0 || req->reservationID != sdrReservationID) &&
600         req->offset)
601     {
602         return ipmi::ccInvalidReservationId;
603     }
604 
605     if (!getSensorSubtree(sensorTree) && sensorTree.empty())
606     {
607         return ipmi::ccResponseError;
608     }
609 
610     size_t fruCount = 0;
611     ipmi_ret_t ret = ipmi::storage::getFruSdrCount(fruCount);
612     if (ret != ipmi::ccSuccess)
613     {
614         return ret;
615     }
616 
617     size_t lastRecord = sensorTree.size() + fruCount - 1;
618     if (req->recordID == lastRecordIndex)
619     {
620         req->recordID = lastRecord;
621     }
622     if (req->recordID > lastRecord)
623     {
624         return ipmi::ccInvalidFieldRequest;
625     }
626 
627     uint16_t nextRecord = lastRecord >= static_cast<size_t>(req->recordID + 1)
628                               ? req->recordID + 1
629                               : 0XFFFF;
630 
631     auto responseClear = static_cast<uint8_t*>(response);
632     std::fill(responseClear, responseClear + requestedSize, 0);
633 
634     auto resp = static_cast<get_sdr::GetSdrResp*>(response);
635     resp->next_record_id_lsb = nextRecord & 0xFF;
636     resp->next_record_id_msb = nextRecord >> 8;
637 
638     if (req->recordID >= sensorTree.size())
639     {
640         size_t fruIndex = req->recordID - sensorTree.size();
641         if (fruIndex >= fruCount)
642         {
643             return ipmi::ccInvalidFieldRequest;
644         }
645         get_sdr::SensorDataFruRecord data;
646         if (req->offset > sizeof(data))
647         {
648             return ipmi::ccInvalidFieldRequest;
649         }
650         ret = ipmi::storage::getFruSdrs(fruIndex, data);
651         if (ret != ipmi::ccSuccess)
652         {
653             return ret;
654         }
655         data.header.record_id_msb = req->recordID << 8;
656         data.header.record_id_lsb = req->recordID & 0xFF;
657         if (sizeof(data) < (req->offset + req->bytesToRead))
658         {
659             req->bytesToRead = sizeof(data) - req->offset;
660         }
661         *dataLen = req->bytesToRead + 2; // next record
662         std::memcpy(&resp->record_data, (char*)&data + req->offset,
663                     req->bytesToRead);
664         return ipmi::ccSuccess;
665     }
666 
667     std::string connection;
668     std::string path;
669     uint16_t sensorIndex = req->recordID;
670     for (const auto& sensor : sensorTree)
671     {
672         if (sensorIndex-- == 0)
673         {
674             if (!sensor.second.size())
675             {
676                 return ipmi::ccResponseError;
677             }
678             connection = sensor.second.begin()->first;
679             path = sensor.first;
680             break;
681         }
682     }
683 
684     SensorMap sensorMap;
685     if (!getSensorMap(connection, path, sensorMap))
686     {
687         return ipmi::ccResponseError;
688     }
689     uint8_t sensornumber = (req->recordID & 0xFF);
690     get_sdr::SensorDataFullRecord record = {};
691 
692     record.header.record_id_msb = req->recordID << 8;
693     record.header.record_id_lsb = req->recordID & 0xFF;
694     record.header.sdr_version = ipmiSdrVersion;
695     record.header.record_type = get_sdr::SENSOR_DATA_FULL_RECORD;
696     record.header.record_length = sizeof(get_sdr::SensorDataFullRecord) -
697                                   sizeof(get_sdr::SensorDataRecordHeader);
698     record.key.owner_id = 0x20;
699     record.key.owner_lun = 0x0;
700     record.key.sensor_number = sensornumber;
701 
702     record.body.entity_id = 0x0;
703     record.body.entity_instance = 0x01;
704     record.body.sensor_capabilities = 0x60; // auto rearm - todo hysteresis
705     record.body.sensor_type = getSensorTypeFromPath(path);
706     std::string type = getSensorTypeStringFromPath(path);
707     auto typeCstr = type.c_str();
708     auto findUnits = sensorUnits.find(typeCstr);
709     if (findUnits != sensorUnits.end())
710     {
711         record.body.sensor_units_2_base =
712             static_cast<uint8_t>(findUnits->second);
713     } // else default 0x0 unspecified
714 
715     record.body.event_reading_type = getSensorEventTypeFromPath(path);
716 
717     auto sensorObject = sensorMap.find("xyz.openbmc_project.Sensor.Value");
718     if (sensorObject == sensorMap.end())
719     {
720         return ipmi::ccResponseError;
721     }
722 
723     auto maxObject = sensorObject->second.find("MaxValue");
724     auto minObject = sensorObject->second.find("MinValue");
725     double max = 128;
726     double min = -127;
727     if (maxObject != sensorObject->second.end())
728     {
729         max = std::visit(VariantToDoubleVisitor(), maxObject->second);
730     }
731 
732     if (minObject != sensorObject->second.end())
733     {
734         min = std::visit(VariantToDoubleVisitor(), minObject->second);
735     }
736 
737     int16_t mValue;
738     int8_t rExp;
739     int16_t bValue;
740     int8_t bExp;
741     bool bSigned;
742 
743     if (!getSensorAttributes(max, min, mValue, rExp, bValue, bExp, bSigned))
744     {
745         return ipmi::ccResponseError;
746     }
747 
748     // apply M, B, and exponents, M and B are 10 bit values, exponents are 4
749     record.body.m_lsb = mValue & 0xFF;
750 
751     // move the smallest bit of the MSB into place (bit 9)
752     // the MSbs are bits 7:8 in m_msb_and_tolerance
753     uint8_t mMsb = (mValue & (1 << 8)) > 0 ? (1 << 6) : 0;
754 
755     // assign the negative
756     if (mValue < 0)
757     {
758         mMsb |= (1 << 7);
759     }
760     record.body.m_msb_and_tolerance = mMsb;
761 
762     record.body.b_lsb = bValue & 0xFF;
763 
764     // move the smallest bit of the MSB into place
765     // the MSbs are bits 7:8 in b_msb_and_accuracy_lsb
766     uint8_t bMsb = (bValue & (1 << 8)) > 0 ? (1 << 6) : 0;
767 
768     // assign the negative
769     if (bValue < 0)
770     {
771         bMsb |= (1 << 7);
772     }
773     record.body.b_msb_and_accuracy_lsb = bMsb;
774 
775     record.body.r_b_exponents = bExp & 0x7;
776     if (bExp < 0)
777     {
778         record.body.r_b_exponents |= 1 << 3;
779     }
780     record.body.r_b_exponents = (rExp & 0x7) << 4;
781     if (rExp < 0)
782     {
783         record.body.r_b_exponents |= 1 << 7;
784     }
785 
786     // todo fill out rest of units
787     if (bSigned)
788     {
789         record.body.sensor_units_1 = 1 << 7;
790     }
791 
792     // populate sensor name from path
793     std::string name;
794     size_t nameStart = path.rfind("/");
795     if (nameStart != std::string::npos)
796     {
797         name = path.substr(nameStart + 1, std::string::npos - nameStart);
798     }
799 
800     std::replace(name.begin(), name.end(), '_', ' ');
801     if (name.size() > FULL_RECORD_ID_STR_MAX_LENGTH)
802     {
803         name.resize(FULL_RECORD_ID_STR_MAX_LENGTH);
804     }
805     record.body.id_string_info = name.size();
806     std::strncpy(record.body.id_string, name.c_str(),
807                  sizeof(record.body.id_string));
808 
809     if (sizeof(get_sdr::SensorDataFullRecord) <
810         (req->offset + req->bytesToRead))
811     {
812         req->bytesToRead = sizeof(get_sdr::SensorDataFullRecord) - req->offset;
813     }
814 
815     *dataLen = 2 +
816                req->bytesToRead; // bytesToRead + MSB and LSB of next record id
817 
818     std::memcpy(&resp->record_data, (char*)&record + req->offset,
819                 req->bytesToRead);
820 
821     return ipmi::ccSuccess;
822 }
823 
getSensorConnectionByName(std::string & name,std::string & connection,std::string & path)824 static int getSensorConnectionByName(std::string& name, std::string& connection,
825                                      std::string& path)
826 {
827     if (!getSensorSubtree(sensorTree) && sensorTree.empty())
828     {
829         return -1;
830     }
831 
832     for (const auto& sensor : sensorTree)
833     {
834         path = sensor.first;
835         if (path.find(name) != std::string::npos)
836         {
837             connection = sensor.second.begin()->first;
838             return 0;
839         }
840     }
841     return -1;
842 }
843 
getSensorThreshold(std::string & name,std::string & thresholdStr)844 int getSensorThreshold(std::string& name, std::string& thresholdStr)
845 {
846     std::string connection;
847     std::string path;
848     int ret = -1;
849     thresholdStr = "";
850 
851     ret = getSensorConnectionByName(name, connection, path);
852     if (ret < 0)
853     {
854         return ret;
855     }
856 
857     SensorMap sensorMap;
858     if (!getSensorMap(connection, path, sensorMap))
859     {
860         return ret;
861     }
862 
863     // Iterate threshold interfaces with priority order
864     for (auto& interface : thresholdCheckedOrder)
865     {
866         auto interfaceProperty = alarmProperties.find(interface);
867         if (interfaceProperty == alarmProperties.end())
868         {
869             continue;
870         }
871 
872         auto propertyValue = interfaceProperty->second;
873 
874         // Checks threshold properties value in sensorMap
875         auto thresholdInterfaceSensorMap = sensorMap.find(interface);
876 
877         // Ignore if interface not set
878         if (thresholdInterfaceSensorMap == sensorMap.end())
879         {
880             continue;
881         }
882 
883         auto& thresholdMap = thresholdInterfaceSensorMap->second;
884 
885         auto& propertyAlarmHigh = propertyValue.at(AlarmType::high);
886         auto alarmHigh = thresholdMap.find(propertyAlarmHigh.name);
887         if (alarmHigh != thresholdMap.end())
888         {
889             if (std::get<bool>(alarmHigh->second))
890             {
891                 thresholdStr = propertyAlarmHigh.threshold;
892                 break;
893             }
894         }
895 
896         auto& propertyAlarmLow = propertyValue.at(AlarmType::low);
897         auto alarmLow = thresholdMap.find(propertyAlarmLow.name);
898         if (alarmLow != thresholdMap.end())
899         {
900             if (std::get<bool>(alarmLow->second))
901             {
902                 thresholdStr = propertyAlarmLow.threshold;
903                 break;
904             }
905         }
906     }
907 
908     return 0;
909 }
910 
getSensorValue(std::string & name,double & val)911 int getSensorValue(std::string& name, double& val)
912 {
913     std::string connection;
914     std::string path;
915     int ret = -1;
916 
917     ret = getSensorConnectionByName(name, connection, path);
918     if (ret < 0)
919     {
920         return ret;
921     }
922 
923     SensorMap sensorMap;
924     if (!getSensorMap(connection, path, sensorMap))
925     {
926         return ret;
927     }
928     auto sensorObject = sensorMap.find("xyz.openbmc_project.Sensor.Value");
929 
930     if (sensorObject == sensorMap.end() ||
931         sensorObject->second.find("Value") == sensorObject->second.end())
932     {
933         return ret;
934     }
935     auto& valueVariant = sensorObject->second["Value"];
936     val = std::visit(VariantToDoubleVisitor(), valueVariant);
937 
938     return 0;
939 }
940 
941 const static boost::container::flat_map<const char*, std::string, CmpStr>
942     sensorUnitStr{{{"temperature", "C"},
943                    {"voltage", "V"},
944                    {"current", "mA"},
945                    {"fan_tach", "RPM"},
946                    {"fan_pwm", "RPM"},
947                    {"power", "W"}}};
948 
getSensorUnit(std::string & name,std::string & unit)949 int getSensorUnit(std::string& name, std::string& unit)
950 {
951     std::string connection;
952     std::string path;
953     int ret = -1;
954 
955     ret = getSensorConnectionByName(name, connection, path);
956     if (ret < 0)
957     {
958         return ret;
959     }
960 
961     std::string sensorTypeStr = getSensorTypeStringFromPath(path);
962     auto findSensor = sensorUnitStr.find(sensorTypeStr.c_str());
963     if (findSensor != sensorUnitStr.end())
964     {
965         unit = findSensor->second;
966         return 0;
967     }
968     else
969         return -1;
970 }
971 
ipmiStorageGetFRUInvAreaInfo(ipmi_netfn_t,ipmi_cmd_t,ipmi_request_t request,ipmi_response_t response,ipmi_data_len_t dataLen,ipmi_context_t)972 ipmi_ret_t ipmiStorageGetFRUInvAreaInfo(
973     ipmi_netfn_t, ipmi_cmd_t, ipmi_request_t request, 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::ccReqDataLenInvalid;
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::ccInvalidFieldRequest;
987     }
988     ipmi_ret_t status = replaceCacheFru(reqDev);
989 
990     if (status != ipmi::ccSuccess)
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::ccSuccess;
1002 }
1003 
registerStorageFunctions()1004 void registerStorageFunctions()
1005 {
1006     // <Get FRU Inventory Area Info>
1007     ipmiPrintAndRegister(
1008         ipmi::netFnStorage,
1009         static_cast<ipmi_cmd_t>(IPMINetfnStorageCmds::ipmiCmdGetFRUInvAreaInfo),
1010         NULL, ipmiStorageGetFRUInvAreaInfo, PRIVILEGE_OPERATOR);
1011 
1012     // <READ FRU Data>
1013     ipmiPrintAndRegister(
1014         ipmi::netFnStorage,
1015         static_cast<ipmi_cmd_t>(IPMINetfnStorageCmds::ipmiCmdReadFRUData), NULL,
1016         ipmiStorageReadFRUData, PRIVILEGE_OPERATOR);
1017 
1018     // <WRITE FRU Data>
1019     ipmiPrintAndRegister(
1020         ipmi::netFnStorage,
1021         static_cast<ipmi_cmd_t>(IPMINetfnStorageCmds::ipmiCmdWriteFRUData),
1022         NULL, ipmiStorageWriteFRUData, PRIVILEGE_OPERATOR);
1023 
1024     // <Reserve SDR Repo>
1025     ipmiPrintAndRegister(
1026         ipmi::netFnStorage,
1027         static_cast<ipmi_cmd_t>(IPMINetfnStorageCmds::ipmiCmdReserveSDR),
1028         nullptr, ipmiStorageReserveSDR, PRIVILEGE_USER);
1029 
1030     // <Get Sdr>
1031     ipmiPrintAndRegister(
1032         ipmi::netFnStorage,
1033         static_cast<ipmi_cmd_t>(IPMINetfnStorageCmds::ipmiCmdGetSDR), nullptr,
1034         ipmiStorageGetSDR, PRIVILEGE_USER);
1035     return;
1036 }
1037 } // namespace storage
1038 } // namespace ipmi
1039