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 "IntelCPUSensor.hpp"
18 
19 #include "SensorPaths.hpp"
20 #include "Thresholds.hpp"
21 #include "Utils.hpp"
22 #include "sensor.hpp"
23 
24 #include <fcntl.h>
25 #include <unistd.h>
26 
27 #include <boost/algorithm/string/replace.hpp>
28 #include <boost/asio/error.hpp>
29 #include <boost/asio/io_context.hpp>
30 #include <boost/asio/posix/descriptor_base.hpp>
31 #include <boost/container/flat_map.hpp>
32 #include <sdbusplus/asio/connection.hpp>
33 #include <sdbusplus/asio/object_server.hpp>
34 
35 #include <algorithm>
36 #include <chrono>
37 #include <cstddef>
38 #include <cstdint>
39 #include <functional>
40 #include <iostream>
41 #include <limits>
42 #include <memory>
43 #include <stdexcept>
44 #include <string>
45 #include <tuple>
46 #include <utility>
47 #include <vector>
48 
IntelCPUSensor(const std::string & path,const std::string & objectType,sdbusplus::asio::object_server & objectServer,std::shared_ptr<sdbusplus::asio::connection> & conn,boost::asio::io_context & io,const std::string & sensorName,std::vector<thresholds::Threshold> && thresholdsIn,const std::string & sensorConfiguration,int cpuId,bool show,double dtsOffset)49 IntelCPUSensor::IntelCPUSensor(
50     const std::string& path, const std::string& objectType,
51     sdbusplus::asio::object_server& objectServer,
52     std::shared_ptr<sdbusplus::asio::connection>& conn,
53     boost::asio::io_context& io, const std::string& sensorName,
54     std::vector<thresholds::Threshold>&& thresholdsIn,
55     const std::string& sensorConfiguration, int cpuId, bool show,
56     double dtsOffset) :
57     Sensor(escapeName(sensorName), std::move(thresholdsIn), sensorConfiguration,
58            objectType, false, false, 0, 0, conn, PowerState::on),
59     objServer(objectServer), inputDev(io), waitTimer(io),
60     nameTcontrol("Tcontrol CPU" + std::to_string(cpuId)), path(path),
61     privTcontrol(std::numeric_limits<double>::quiet_NaN()),
62     dtsOffset(dtsOffset), show(show), pollTime(IntelCPUSensor::sensorPollMs)
63 
64 {
65     if (show)
66     {
67         if (auto fileParts = splitFileName(path))
68         {
69             auto& [type, nr, item] = *fileParts;
70             std::string interfacePath;
71             const char* units = nullptr;
72             if (type == "power")
73             {
74                 interfacePath = "/xyz/openbmc_project/sensors/power/" + name;
75                 units = sensor_paths::unitWatts;
76                 minValue = 0;
77                 maxValue = 511;
78             }
79             else
80             {
81                 interfacePath = "/xyz/openbmc_project/sensors/temperature/" +
82                                 name;
83                 units = sensor_paths::unitDegreesC;
84                 minValue = -128;
85                 maxValue = 127;
86             }
87 
88             sensorInterface = objectServer.add_interface(
89                 interfacePath, "xyz.openbmc_project.Sensor.Value");
90             for (const auto& threshold : thresholds)
91             {
92                 std::string interface =
93                     thresholds::getInterface(threshold.level);
94                 thresholdInterfaces[static_cast<size_t>(threshold.level)] =
95                     objectServer.add_interface(interfacePath, interface);
96             }
97             association = objectServer.add_interface(interfacePath,
98                                                      association::interface);
99 
100             setInitialProperties(units);
101         }
102     }
103 
104     // call setup always as not all sensors call setInitialProperties
105     setupPowerMatch(conn);
106 }
107 
~IntelCPUSensor()108 IntelCPUSensor::~IntelCPUSensor()
109 {
110     // close the input dev to cancel async operations
111     inputDev.close();
112     waitTimer.cancel();
113     if (show)
114     {
115         for (const auto& iface : thresholdInterfaces)
116         {
117             objServer.remove_interface(iface);
118         }
119         objServer.remove_interface(sensorInterface);
120         objServer.remove_interface(association);
121         objServer.remove_interface(availableInterface);
122         objServer.remove_interface(operationalInterface);
123     }
124 }
125 
restartRead()126 void IntelCPUSensor::restartRead()
127 {
128     std::weak_ptr<IntelCPUSensor> weakRef = weak_from_this();
129     waitTimer.expires_after(std::chrono::milliseconds(pollTime));
130     waitTimer.async_wait([weakRef](const boost::system::error_code& ec) {
131         if (ec == boost::asio::error::operation_aborted)
132         {
133             std::cerr << "Failed to reschedule\n";
134             return;
135         }
136         std::shared_ptr<IntelCPUSensor> self = weakRef.lock();
137 
138         if (self)
139         {
140             self->setupRead();
141         }
142     });
143 }
144 
setupRead()145 void IntelCPUSensor::setupRead()
146 {
147     if (readingStateGood())
148     {
149         inputDev.close();
150 
151         // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
152         fd = open(path.c_str(), O_RDONLY | O_NONBLOCK);
153         if (fd < 0)
154         {
155             std::cerr << name << " unable to open fd!\n";
156             return;
157         }
158 
159         inputDev.assign(fd);
160     }
161     else
162     {
163         markAvailable(false);
164         updateValue(std::numeric_limits<double>::quiet_NaN());
165         restartRead();
166         return;
167     }
168 
169     std::weak_ptr<IntelCPUSensor> weakRef = weak_from_this();
170     inputDev.async_wait(boost::asio::posix::descriptor_base::wait_read,
171                         [weakRef](const boost::system::error_code& ec) {
172                             std::shared_ptr<IntelCPUSensor> self =
173                                 weakRef.lock();
174 
175                             if (self)
176                             {
177                                 self->handleResponse(ec);
178                             }
179                         });
180 }
181 
updateMinMaxValues()182 void IntelCPUSensor::updateMinMaxValues()
183 {
184     const boost::container::flat_map<
185         std::string,
186         std::vector<std::tuple<const char*, std::reference_wrapper<double>,
187                                const char*>>>
188         map = {
189             {
190                 "cap",
191                 {
192                     std::make_tuple("cap_max", std::ref(maxValue), "MaxValue"),
193                     std::make_tuple("cap_min", std::ref(minValue), "MinValue"),
194                 },
195             },
196         };
197 
198     if (auto fileParts = splitFileName(path))
199     {
200         auto& [fileType, fileNr, fileItem] = *fileParts;
201         const auto mapIt = map.find(fileItem);
202         if (mapIt != map.cend())
203         {
204             for (const auto& vectorItem : mapIt->second)
205             {
206                 const auto& [suffix, oldValue, dbusName] = vectorItem;
207                 auto attrPath = boost::replace_all_copy(path, fileItem, suffix);
208                 if (auto newVal =
209                         readFile(attrPath, IntelCPUSensor::sensorScaleFactor))
210                 {
211                     updateProperty(sensorInterface, oldValue, *newVal,
212                                    dbusName);
213                 }
214                 else
215                 {
216                     if (isPowerOn())
217                     {
218                         updateProperty(sensorInterface, oldValue, 0, dbusName);
219                     }
220                     else
221                     {
222                         updateProperty(sensorInterface, oldValue,
223                                        std::numeric_limits<double>::quiet_NaN(),
224                                        dbusName);
225                     }
226                 }
227             }
228         }
229     }
230 }
231 
handleResponse(const boost::system::error_code & err)232 void IntelCPUSensor::handleResponse(const boost::system::error_code& err)
233 {
234     if ((err == boost::system::errc::bad_file_descriptor) ||
235         (err == boost::asio::error::misc_errors::not_found))
236     {
237         return; // we're being destroyed
238     }
239     if (err == boost::system::errc::operation_canceled)
240     {
241         if (readingStateGood())
242         {
243             if (!loggedInterfaceDown)
244             {
245                 std::cerr << name << " interface down!\n";
246                 loggedInterfaceDown = true;
247             }
248             pollTime = static_cast<size_t>(IntelCPUSensor::sensorPollMs) * 10U;
249             markFunctional(false);
250         }
251         return;
252     }
253     loggedInterfaceDown = false;
254 
255     if (err)
256     {
257         pollTime = sensorFailedPollTimeMs;
258         incrementError();
259         return;
260     }
261 
262     static constexpr uint32_t bufLen = 128;
263     std::string response;
264     response.resize(bufLen);
265     int rdLen = 0;
266 
267     if (fd >= 0)
268     {
269         rdLen = pread(fd, response.data(), bufLen, 0);
270     }
271 
272     if (rdLen > 0)
273     {
274         try
275         {
276             rawValue = std::stod(response);
277             double nvalue = rawValue / IntelCPUSensor::sensorScaleFactor;
278 
279             if (show)
280             {
281                 updateValue(nvalue);
282             }
283             else
284             {
285                 value = nvalue;
286             }
287             if (minMaxReadCounter++ % 8 == 0)
288             {
289                 updateMinMaxValues();
290             }
291 
292             double gTcontrol = gCpuSensors[nameTcontrol]
293                                    ? gCpuSensors[nameTcontrol]->value
294                                    : std::numeric_limits<double>::quiet_NaN();
295             if (gTcontrol != privTcontrol)
296             {
297                 privTcontrol = gTcontrol;
298 
299                 if (!thresholds.empty())
300                 {
301                     std::vector<thresholds::Threshold> newThresholds;
302                     if (parseThresholdsFromAttr(
303                             newThresholds, path,
304                             IntelCPUSensor::sensorScaleFactor, dtsOffset, 0))
305                     {
306                         if (!std::equal(thresholds.begin(), thresholds.end(),
307                                         newThresholds.begin(),
308                                         newThresholds.end()))
309                         {
310                             thresholds = newThresholds;
311                             if (show)
312                             {
313                                 thresholds::updateThresholds(this);
314                             }
315                         }
316                     }
317                     else
318                     {
319                         std::cerr << "Failure to update thresholds for " << name
320                                   << "\n";
321                     }
322                 }
323             }
324         }
325         catch (const std::invalid_argument&)
326         {
327             incrementError();
328         }
329     }
330     else
331     {
332         pollTime = sensorFailedPollTimeMs;
333         incrementError();
334     }
335     restartRead();
336 }
337 
checkThresholds()338 void IntelCPUSensor::checkThresholds()
339 {
340     if (show)
341     {
342         thresholds::checkThresholds(this);
343     }
344 }
345