1 /*
2 // Copyright (c) 2018 Intel Corporation
3 //
4 // Licensed under the Apache License, Version 2.0 (the "License");
5 // you may not use this file except in compliance with the License.
6 // You may obtain a copy of the License at
7 //
8 //      http://www.apache.org/licenses/LICENSE-2.0
9 //
10 // Unless required by applicable law or agreed to in writing, software
11 // distributed under the License is distributed on an "AS IS" BASIS,
12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 // See the License for the specific language governing permissions and
14 // limitations under the License.
15 */
16 
17 #include <boost/algorithm/string.hpp>
18 #include <boost/bimap.hpp>
19 #include <boost/container/flat_map.hpp>
20 #include <cstdio>
21 #include <cstring>
22 #include <exception>
23 #include <filesystem>
24 #include <ipmid/api.hpp>
25 #include <ipmid/types.hpp>
26 #include <map>
27 #include <optional>
28 #include <phosphor-logging/log.hpp>
29 #include <sdbusplus/bus/match.hpp>
30 #include <string>
31 #include <unordered_set>
32 #include <vector>
33 
34 #pragma once
35 
36 static constexpr bool debug = false;
37 
38 struct CmpStrVersion
39 {
40     bool operator()(std::string a, std::string b) const
41     {
42         return strverscmp(a.c_str(), b.c_str()) < 0;
43     }
44 };
45 
46 using SensorSubTree = boost::container::flat_map<
47     std::string,
48     boost::container::flat_map<std::string, std::vector<std::string>>,
49     CmpStrVersion>;
50 
51 using SensorNumMap = boost::bimap<int, std::string>;
52 
53 static constexpr uint16_t maxSensorsPerLUN = 255;
54 static constexpr uint16_t maxIPMISensors = (maxSensorsPerLUN * 3);
55 static constexpr uint16_t lun1Sensor0 = 0x100;
56 static constexpr uint16_t lun3Sensor0 = 0x300;
57 static constexpr uint16_t invalidSensorNumber = 0xFFFF;
58 static constexpr uint8_t reservedSensorNumber = 0xFF;
59 
60 namespace details
61 {
62 // Enable/disable the logging of stats instrumentation
63 static constexpr bool enableInstrumentation = false;
64 
65 class IPMIStatsEntry
66 {
67   private:
68     int numReadings = 0;
69     int numMissings = 0;
70     int numStreakRead = 0;
71     int numStreakMiss = 0;
72     double minValue = 0.0;
73     double maxValue = 0.0;
74     std::string sensorName;
75 
76   public:
77     const std::string& getName(void) const
78     {
79         return sensorName;
80     }
81 
82     void updateName(std::string_view name)
83     {
84         sensorName = name;
85     }
86 
87     // Returns true if this is the first successful reading
88     // This is so the caller can log the coefficients used
89     bool updateReading(double reading, int raw)
90     {
91         if constexpr (!enableInstrumentation)
92         {
93             return false;
94         }
95 
96         bool first = ((numReadings == 0) && (numMissings == 0));
97 
98         // Sensors can use "nan" to indicate unavailable reading
99         if (!(std::isfinite(reading)))
100         {
101             // Only show this if beginning a new streak
102             if (numStreakMiss == 0)
103             {
104                 std::cerr << "IPMI sensor " << sensorName
105                           << ": Missing reading, byte=" << raw
106                           << ", Reading counts good=" << numReadings
107                           << " miss=" << numMissings
108                           << ", Prior good streak=" << numStreakRead << "\n";
109             }
110 
111             numStreakRead = 0;
112             ++numMissings;
113             ++numStreakMiss;
114 
115             return first;
116         }
117 
118         // Only show this if beginning a new streak and not the first time
119         if ((numStreakRead == 0) && (numReadings != 0))
120         {
121             std::cerr << "IPMI sensor " << sensorName
122                       << ": Recovered reading, value=" << reading
123                       << " byte=" << raw
124                       << ", Reading counts good=" << numReadings
125                       << " miss=" << numMissings
126                       << ", Prior miss streak=" << numStreakMiss << "\n";
127         }
128 
129         // Initialize min/max if the first successful reading
130         if (numReadings == 0)
131         {
132             std::cerr << "IPMI sensor " << sensorName
133                       << ": First reading, value=" << reading << " byte=" << raw
134                       << "\n";
135 
136             minValue = reading;
137             maxValue = reading;
138         }
139 
140         numStreakMiss = 0;
141         ++numReadings;
142         ++numStreakRead;
143 
144         // Only provide subsequent output if new min/max established
145         if (reading < minValue)
146         {
147             std::cerr << "IPMI sensor " << sensorName
148                       << ": Lowest reading, value=" << reading
149                       << " byte=" << raw << "\n";
150 
151             minValue = reading;
152         }
153 
154         if (reading > maxValue)
155         {
156             std::cerr << "IPMI sensor " << sensorName
157                       << ": Highest reading, value=" << reading
158                       << " byte=" << raw << "\n";
159 
160             maxValue = reading;
161         }
162 
163         return first;
164     }
165 };
166 
167 class IPMIStatsTable
168 {
169   private:
170     std::vector<IPMIStatsEntry> entries;
171 
172   private:
173     void padEntries(size_t index)
174     {
175         char hexbuf[16];
176 
177         // Pad vector until entries[index] becomes a valid index
178         while (entries.size() <= index)
179         {
180             // As name not known yet, use human-readable hex as name
181             IPMIStatsEntry newEntry;
182             sprintf(hexbuf, "0x%02zX", entries.size());
183             newEntry.updateName(hexbuf);
184 
185             entries.push_back(std::move(newEntry));
186         }
187     }
188 
189   public:
190     void wipeTable(void)
191     {
192         entries.clear();
193     }
194 
195     const std::string& getName(size_t index)
196     {
197         padEntries(index);
198         return entries[index].getName();
199     }
200 
201     void updateName(size_t index, std::string_view name)
202     {
203         padEntries(index);
204         entries[index].updateName(name);
205     }
206 
207     bool updateReading(size_t index, double reading, int raw)
208     {
209         padEntries(index);
210         return entries[index].updateReading(reading, raw);
211     }
212 };
213 
214 class IPMIWriteEntry
215 {
216   private:
217     bool writePermission = false;
218 
219   public:
220     bool getWritePermission(void) const
221     {
222         return writePermission;
223     }
224 
225     void setWritePermission(bool permission)
226     {
227         writePermission = permission;
228     }
229 };
230 
231 class IPMIWriteTable
232 {
233   private:
234     std::vector<IPMIWriteEntry> entries;
235 
236   private:
237     void padEntries(size_t index)
238     {
239         // Pad vector until entries[index] becomes a valid index
240         if (entries.size() <= index)
241         {
242             entries.resize(index + 1);
243         }
244     }
245 
246   public:
247     void wipeTable(void)
248     {
249         entries.clear();
250     }
251 
252     bool getWritePermission(size_t index)
253     {
254         padEntries(index);
255         return entries[index].getWritePermission();
256     }
257 
258     void setWritePermission(size_t index, bool permission)
259     {
260         padEntries(index);
261         entries[index].setWritePermission(permission);
262     }
263 };
264 
265 // Store information for threshold sensors and they are not used by VR
266 // sensors. These objects are global singletons, used from a variety of places.
267 inline IPMIStatsTable sdrStatsTable;
268 inline IPMIWriteTable sdrWriteTable;
269 
270 /**
271  * Search ObjectMapper for sensors and update them to subtree.
272  *
273  * The function will search for sensors under either
274  * /xyz/openbmc_project/sensors or /xyz/openbmc_project/extsensors. It will
275  * optionally search VR typed sensors under /xyz/openbmc_project/vr
276  *
277  * @return the updated amount of times any of "sensors" or "extsensors" sensor
278  * paths updated successfully, previous amount if all failed. The "vr"
279  * sensor path is optional, and does not participate in the return value.
280  */
281 uint16_t getSensorSubtree(std::shared_ptr<SensorSubTree>& subtree);
282 
283 bool getSensorNumMap(std::shared_ptr<SensorNumMap>& sensorNumMap);
284 } // namespace details
285 
286 bool getSensorSubtree(SensorSubTree& subtree);
287 
288 #ifdef FEATURE_HYBRID_SENSORS
289 ipmi::sensor::IdInfoMap::const_iterator
290     findStaticSensor(const std::string& path);
291 #endif
292 
293 struct CmpStr
294 {
295     bool operator()(const char* a, const char* b) const
296     {
297         return std::strcmp(a, b) < 0;
298     }
299 };
300 
301 static constexpr size_t sensorTypeCodes = 0;
302 static constexpr size_t sensorEventTypeCodes = 1;
303 
304 enum class SensorTypeCodes : uint8_t
305 {
306     reserved = 0x00,
307     temperature = 0x01,
308     voltage = 0x02,
309     current = 0x03,
310     fan = 0x04,
311     physical_security = 0x5,
312     processor = 0x07,
313     power_unit = 0x09,
314     other = 0x0b,
315     memory = 0x0c,
316     buttons = 0x14,
317     watchdog2 = 0x23,
318     entity = 0x25,
319 };
320 
321 enum class SensorEventTypeCodes : uint8_t
322 {
323     unspecified = 0x00,
324     threshold = 0x01,
325     sensorSpecified = 0x6f
326 };
327 
328 const static boost::container::flat_map<
329     const char*, std::pair<SensorTypeCodes, SensorEventTypeCodes>, CmpStr>
330     sensorTypes{
331         {{"temperature", std::make_pair(SensorTypeCodes::temperature,
332                                         SensorEventTypeCodes::threshold)},
333          {"voltage", std::make_pair(SensorTypeCodes::voltage,
334                                     SensorEventTypeCodes::threshold)},
335          {"current", std::make_pair(SensorTypeCodes::current,
336                                     SensorEventTypeCodes::threshold)},
337          {"fan_tach", std::make_pair(SensorTypeCodes::fan,
338                                      SensorEventTypeCodes::threshold)},
339          {"fan_pwm", std::make_pair(SensorTypeCodes::fan,
340                                     SensorEventTypeCodes::threshold)},
341          {"intrusion", std::make_pair(SensorTypeCodes::physical_security,
342                                       SensorEventTypeCodes::sensorSpecified)},
343          {"processor", std::make_pair(SensorTypeCodes::processor,
344                                       SensorEventTypeCodes::sensorSpecified)},
345          {"power", std::make_pair(SensorTypeCodes::other,
346                                   SensorEventTypeCodes::threshold)},
347          {"memory", std::make_pair(SensorTypeCodes::memory,
348                                    SensorEventTypeCodes::sensorSpecified)},
349          {"state", std::make_pair(SensorTypeCodes::power_unit,
350                                   SensorEventTypeCodes::sensorSpecified)},
351          {"buttons", std::make_pair(SensorTypeCodes::buttons,
352                                     SensorEventTypeCodes::sensorSpecified)},
353          {"watchdog", std::make_pair(SensorTypeCodes::watchdog2,
354                                      SensorEventTypeCodes::sensorSpecified)},
355          {"entity", std::make_pair(SensorTypeCodes::entity,
356                                    SensorEventTypeCodes::sensorSpecified)}}};
357 std::string getSensorTypeStringFromPath(const std::string& path);
358 
359 uint8_t getSensorTypeFromPath(const std::string& path);
360 
361 uint16_t getSensorNumberFromPath(const std::string& path);
362 
363 uint8_t getSensorEventTypeFromPath(const std::string& path);
364 
365 std::string getPathFromSensorNumber(uint16_t sensorNum);
366 
367 namespace ipmi
368 {
369 std::map<std::string, std::vector<std::string>>
370     getObjectInterfaces(const char* path);
371 
372 std::map<std::string, Value> getEntityManagerProperties(const char* path,
373                                                         const char* interface);
374 
375 std::optional<std::unordered_set<std::string>>&
376     getIpmiDecoratorPaths(const std::optional<ipmi::Context::ptr>& ctx);
377 
378 const std::string* getSensorConfigurationInterface(
379     const std::map<std::string, std::vector<std::string>>&
380         sensorInterfacesResponse);
381 
382 void updateIpmiFromAssociation(
383     const std::string& path,
384     const std::unordered_set<std::string>& ipmiDecoratorPaths,
385     const DbusInterfaceMap& sensorMap, uint8_t& entityId,
386     uint8_t& entityInstance);
387 } // namespace ipmi
388