xref: /openbmc/phosphor-pid-control/pid/zone.cpp (revision c179d406)
1 /**
2  * Copyright 2017 Google Inc.
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 /* Configuration. */
18 #include "zone.hpp"
19 
20 #include "conf.hpp"
21 #include "pid/controller.hpp"
22 #include "pid/ec/pid.hpp"
23 #include "pid/fancontroller.hpp"
24 #include "pid/stepwisecontroller.hpp"
25 #include "pid/thermalcontroller.hpp"
26 
27 #include <algorithm>
28 #include <chrono>
29 #include <cstring>
30 #include <fstream>
31 #include <iostream>
32 #include <libconfig.h++>
33 #include <memory>
34 
35 using tstamp = std::chrono::high_resolution_clock::time_point;
36 using namespace std::literals::chrono_literals;
37 
38 float PIDZone::getMaxRPMRequest(void) const
39 {
40     return _maximumRPMSetPt;
41 }
42 
43 bool PIDZone::getManualMode(void) const
44 {
45     return _manualMode;
46 }
47 
48 void PIDZone::setManualMode(bool mode)
49 {
50     _manualMode = mode;
51 }
52 
53 bool PIDZone::getFailSafeMode(void) const
54 {
55     // If any keys are present at least one sensor is in fail safe mode.
56     return !_failSafeSensors.empty();
57 }
58 
59 int64_t PIDZone::getZoneID(void) const
60 {
61     return _zoneId;
62 }
63 
64 void PIDZone::addRPMSetPoint(float setpoint)
65 {
66     _RPMSetPoints.push_back(setpoint);
67 }
68 
69 void PIDZone::clearRPMSetPoints(void)
70 {
71     _RPMSetPoints.clear();
72 }
73 
74 float PIDZone::getFailSafePercent(void) const
75 {
76     return _failSafePercent;
77 }
78 
79 float PIDZone::getMinThermalRPMSetpoint(void) const
80 {
81     return _minThermalRpmSetPt;
82 }
83 
84 void PIDZone::addFanPID(std::unique_ptr<Controller> pid)
85 {
86     _fans.push_back(std::move(pid));
87 }
88 
89 void PIDZone::addThermalPID(std::unique_ptr<Controller> pid)
90 {
91     _thermals.push_back(std::move(pid));
92 }
93 
94 double PIDZone::getCachedValue(const std::string& name)
95 {
96     return _cachedValuesByName.at(name);
97 }
98 
99 void PIDZone::addFanInput(const std::string& fan)
100 {
101     _fanInputs.push_back(fan);
102 }
103 
104 void PIDZone::addThermalInput(const std::string& therm)
105 {
106     _thermalInputs.push_back(therm);
107 }
108 
109 void PIDZone::determineMaxRPMRequest(void)
110 {
111     float max = 0;
112     std::vector<float>::iterator result;
113 
114     if (_RPMSetPoints.size() > 0)
115     {
116         result = std::max_element(_RPMSetPoints.begin(), _RPMSetPoints.end());
117         max = *result;
118     }
119 
120     /*
121      * If the maximum RPM set-point output is below the minimum RPM
122      * set-point, set it to the minimum.
123      */
124     max = std::max(getMinThermalRPMSetpoint(), max);
125 
126 #ifdef __TUNING_LOGGING__
127     /*
128      * We received no set-points from thermal sensors.
129      * This is a case experienced during tuning where they only specify
130      * fan sensors and one large fan PID for all the fans.
131      */
132     static constexpr auto setpointpath = "/etc/thermal.d/set-point";
133     try
134     {
135         std::ifstream ifs;
136         ifs.open(setpointpath);
137         if (ifs.good())
138         {
139             int value;
140             ifs >> value;
141 
142             /* expecting RPM set-point, not pwm% */
143             max = static_cast<float>(value);
144         }
145     }
146     catch (const std::exception& e)
147     {
148         /* This exception is uninteresting. */
149         std::cerr << "Unable to read from '" << setpointpath << "'\n";
150     }
151 #endif
152 
153     _maximumRPMSetPt = max;
154     return;
155 }
156 
157 #ifdef __TUNING_LOGGING__
158 void PIDZone::initializeLog(void)
159 {
160     /* Print header for log file:
161      * epoch_ms,setpt,fan1,fan2,fanN,sensor1,sensor2,sensorN,failsafe
162      */
163 
164     _log << "epoch_ms,setpt";
165 
166     for (const auto& f : _fanInputs)
167     {
168         _log << "," << f;
169     }
170     for (const auto& t : _thermalInputs)
171     {
172         _log << "," << t;
173     }
174     _log << ",failsafe";
175     _log << std::endl;
176 
177     return;
178 }
179 
180 std::ofstream& PIDZone::getLogHandle(void)
181 {
182     return _log;
183 }
184 #endif
185 
186 /*
187  * TODO(venture) This is effectively updating the cache and should check if the
188  * values they're using to update it are new or old, or whatnot.  For instance,
189  * if we haven't heard from the host in X time we need to detect this failure.
190  *
191  * I haven't decided if the Sensor should have a lastUpdated method or whether
192  * that should be for the ReadInterface or etc...
193  */
194 
195 /**
196  * We want the PID loop to run with values cached, so this will get all the
197  * fan tachs for the loop.
198  */
199 void PIDZone::updateFanTelemetry(void)
200 {
201     /* TODO(venture): Should I just make _log point to /dev/null when logging
202      * is disabled?  I think it's a waste to try and log things even if the
203      * data is just being dropped though.
204      */
205 #ifdef __TUNING_LOGGING__
206     tstamp now = std::chrono::high_resolution_clock::now();
207     _log << std::chrono::duration_cast<std::chrono::milliseconds>(
208                 now.time_since_epoch())
209                 .count();
210     _log << "," << _maximumRPMSetPt;
211 #endif
212 
213     for (const auto& f : _fanInputs)
214     {
215         auto sensor = _mgr.getSensor(f);
216         ReadReturn r = sensor->read();
217         _cachedValuesByName[f] = r.value;
218 
219         /*
220          * TODO(venture): We should check when these were last read.
221          * However, these are the fans, so if I'm not getting updated values
222          * for them... what should I do?
223          */
224 #ifdef __TUNING_LOGGING__
225         _log << "," << r.value;
226 #endif
227     }
228 
229 #ifdef __TUNING_LOGGING__
230     for (const auto& t : _thermalInputs)
231     {
232         _log << "," << _cachedValuesByName[t];
233     }
234 #endif
235 
236     return;
237 }
238 
239 void PIDZone::updateSensors(void)
240 {
241     using namespace std::chrono;
242     /* margin and temp are stored as temp */
243     tstamp now = high_resolution_clock::now();
244 
245     for (const auto& t : _thermalInputs)
246     {
247         auto sensor = _mgr.getSensor(t);
248         ReadReturn r = sensor->read();
249         int64_t timeout = sensor->getTimeout();
250 
251         _cachedValuesByName[t] = r.value;
252         tstamp then = r.updated;
253 
254         if (sensor->getFailed())
255         {
256             _failSafeSensors.insert(t);
257         }
258         /* Only go into failsafe if the timeout is set for
259          * the sensor.
260          */
261         else if (timeout > 0)
262         {
263             auto duration =
264                 duration_cast<std::chrono::seconds>(now - then).count();
265             auto period = std::chrono::seconds(timeout).count();
266             if (duration >= period)
267             {
268                 // std::cerr << "Entering fail safe mode.\n";
269                 _failSafeSensors.insert(t);
270             }
271             else
272             {
273                 // Check if it's in there: remove it.
274                 auto kt = _failSafeSensors.find(t);
275                 if (kt != _failSafeSensors.end())
276                 {
277                     _failSafeSensors.erase(kt);
278                 }
279             }
280         }
281     }
282 
283     return;
284 }
285 
286 void PIDZone::initializeCache(void)
287 {
288     for (const auto& f : _fanInputs)
289     {
290         _cachedValuesByName[f] = 0;
291     }
292 
293     for (const auto& t : _thermalInputs)
294     {
295         _cachedValuesByName[t] = 0;
296 
297         // Start all sensors in fail-safe mode.
298         _failSafeSensors.insert(t);
299     }
300 }
301 
302 void PIDZone::dumpCache(void)
303 {
304     std::cerr << "Cache values now: \n";
305     for (const auto& k : _cachedValuesByName)
306     {
307         std::cerr << k.first << ": " << k.second << "\n";
308     }
309 }
310 
311 void PIDZone::processFans(void)
312 {
313     for (auto& p : _fans)
314     {
315         p->process();
316     }
317 }
318 
319 void PIDZone::processThermals(void)
320 {
321     for (auto& p : _thermals)
322     {
323         p->process();
324     }
325 }
326 
327 Sensor* PIDZone::getSensor(const std::string& name)
328 {
329     return _mgr.getSensor(name);
330 }
331 
332 bool PIDZone::manual(bool value)
333 {
334     std::cerr << "manual: " << value << std::endl;
335     setManualMode(value);
336     return ModeObject::manual(value);
337 }
338 
339 bool PIDZone::failSafe() const
340 {
341     return getFailSafeMode();
342 }
343