xref: /openbmc/phosphor-pid-control/pid/zone.cpp (revision a4146eb1)
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 #include "pid/tuning.hpp"
27 
28 #include <algorithm>
29 #include <chrono>
30 #include <cstring>
31 #include <fstream>
32 #include <iostream>
33 #include <memory>
34 #include <sstream>
35 #include <string>
36 
37 using tstamp = std::chrono::high_resolution_clock::time_point;
38 using namespace std::literals::chrono_literals;
39 
40 // Enforces minimum duration between events
41 // Rreturns true if event should be allowed, false if disallowed
42 bool allowThrottle(const tstamp& now, const std::chrono::seconds& pace)
43 {
44     static tstamp then;
45     static bool first = true;
46 
47     if (first)
48     {
49         // Special case initialization
50         then = now;
51         first = false;
52 
53         // Initialization, always allow
54         return true;
55     }
56 
57     auto elapsed = now - then;
58     if (elapsed < pace)
59     {
60         // Too soon since last time, disallow
61         return false;
62     }
63 
64     // It has been long enough, allow
65     then = now;
66     return true;
67 }
68 
69 namespace pid_control
70 {
71 
72 double DbusPidZone::getMaxSetPointRequest(void) const
73 {
74     return _maximumSetPoint;
75 }
76 
77 bool DbusPidZone::getManualMode(void) const
78 {
79     return _manualMode;
80 }
81 
82 void DbusPidZone::setManualMode(bool mode)
83 {
84     _manualMode = mode;
85 
86     // If returning to automatic mode, need to restore PWM from PID loop
87     if (!mode)
88     {
89         _redundantWrite = true;
90     }
91 }
92 
93 bool DbusPidZone::getFailSafeMode(void) const
94 {
95     // If any keys are present at least one sensor is in fail safe mode.
96     return !_failSafeSensors.empty();
97 }
98 
99 int64_t DbusPidZone::getZoneID(void) const
100 {
101     return _zoneId;
102 }
103 
104 void DbusPidZone::addSetPoint(double setpoint)
105 {
106     _SetPoints.push_back(setpoint);
107 }
108 
109 void DbusPidZone::addRPMCeiling(double ceiling)
110 {
111     _RPMCeilings.push_back(ceiling);
112 }
113 
114 void DbusPidZone::clearRPMCeilings(void)
115 {
116     _RPMCeilings.clear();
117 }
118 
119 void DbusPidZone::clearSetPoints(void)
120 {
121     _SetPoints.clear();
122 }
123 
124 double DbusPidZone::getFailSafePercent(void) const
125 {
126     return _failSafePercent;
127 }
128 
129 double DbusPidZone::getMinThermalSetpoint(void) const
130 {
131     return _minThermalOutputSetPt;
132 }
133 
134 void DbusPidZone::addFanPID(std::unique_ptr<Controller> pid)
135 {
136     _fans.push_back(std::move(pid));
137 }
138 
139 void DbusPidZone::addThermalPID(std::unique_ptr<Controller> pid)
140 {
141     _thermals.push_back(std::move(pid));
142 }
143 
144 double DbusPidZone::getCachedValue(const std::string& name)
145 {
146     return _cachedValuesByName.at(name);
147 }
148 
149 void DbusPidZone::addFanInput(const std::string& fan)
150 {
151     _fanInputs.push_back(fan);
152 }
153 
154 void DbusPidZone::addThermalInput(const std::string& therm)
155 {
156     _thermalInputs.push_back(therm);
157 }
158 
159 // Updates desired RPM setpoint from optional text file
160 // Returns true if rpmValue updated, false if left unchanged
161 static bool fileParseRpm(const std::string& fileName, double& rpmValue)
162 {
163     static constexpr std::chrono::seconds throttlePace{3};
164 
165     std::string errText;
166 
167     try
168     {
169         std::ifstream ifs;
170         ifs.open(fileName);
171         if (ifs)
172         {
173             int value;
174             ifs >> value;
175 
176             if (value <= 0)
177             {
178                 errText = "File content could not be parsed to a number";
179             }
180             else if (value <= 100)
181             {
182                 errText = "File must contain RPM value, not PWM value";
183             }
184             else
185             {
186                 rpmValue = static_cast<double>(value);
187                 return true;
188             }
189         }
190     }
191     catch (const std::exception& e)
192     {
193         errText = "Exception: ";
194         errText += e.what();
195     }
196 
197     // The file is optional, intentionally not an error if file not found
198     if (!(errText.empty()))
199     {
200         tstamp now = std::chrono::high_resolution_clock::now();
201         if (allowThrottle(now, throttlePace))
202         {
203             std::cerr << "Unable to read from '" << fileName << "': " << errText
204                       << "\n";
205         }
206     }
207 
208     return false;
209 }
210 
211 void DbusPidZone::determineMaxSetPointRequest(void)
212 {
213     double max = 0;
214     std::vector<double>::iterator result;
215 
216     if (_SetPoints.size() > 0)
217     {
218         result = std::max_element(_SetPoints.begin(), _SetPoints.end());
219         max = *result;
220     }
221 
222     if (_RPMCeilings.size() > 0)
223     {
224         result = std::min_element(_RPMCeilings.begin(), _RPMCeilings.end());
225         max = std::min(max, *result);
226     }
227 
228     /*
229      * If the maximum RPM setpoint output is below the minimum RPM
230      * setpoint, set it to the minimum.
231      */
232     max = std::max(getMinThermalSetpoint(), max);
233 
234     if (tuningEnabled)
235     {
236         /*
237          * We received no setpoints from thermal sensors.
238          * This is a case experienced during tuning where they only specify
239          * fan sensors and one large fan PID for all the fans.
240          */
241         static constexpr auto setpointpath = "/etc/thermal.d/setpoint";
242 
243         fileParseRpm(setpointpath, max);
244 
245         // Allow per-zone setpoint files to override overall setpoint file
246         std::ostringstream zoneSuffix;
247         zoneSuffix << ".zone" << _zoneId;
248         std::string zoneSetpointPath = setpointpath + zoneSuffix.str();
249 
250         fileParseRpm(zoneSetpointPath, max);
251     }
252 
253     _maximumSetPoint = max;
254     return;
255 }
256 
257 void DbusPidZone::initializeLog(void)
258 {
259     /* Print header for log file:
260      * epoch_ms,setpt,fan1,fan2,fanN,sensor1,sensor2,sensorN,failsafe
261      */
262 
263     _log << "epoch_ms,setpt";
264 
265     for (const auto& f : _fanInputs)
266     {
267         _log << "," << f;
268     }
269     for (const auto& t : _thermalInputs)
270     {
271         _log << "," << t;
272     }
273     _log << ",failsafe";
274     _log << std::endl;
275 
276     return;
277 }
278 
279 void DbusPidZone::writeLog(const std::string& value)
280 {
281     _log << value;
282     return;
283 }
284 
285 /*
286  * TODO(venture) This is effectively updating the cache and should check if the
287  * values they're using to update it are new or old, or whatnot.  For instance,
288  * if we haven't heard from the host in X time we need to detect this failure.
289  *
290  * I haven't decided if the Sensor should have a lastUpdated method or whether
291  * that should be for the ReadInterface or etc...
292  */
293 
294 /**
295  * We want the PID loop to run with values cached, so this will get all the
296  * fan tachs for the loop.
297  */
298 void DbusPidZone::updateFanTelemetry(void)
299 {
300     /* TODO(venture): Should I just make _log point to /dev/null when logging
301      * is disabled?  I think it's a waste to try and log things even if the
302      * data is just being dropped though.
303      */
304     tstamp now = std::chrono::high_resolution_clock::now();
305     if (loggingEnabled)
306     {
307         _log << std::chrono::duration_cast<std::chrono::milliseconds>(
308                     now.time_since_epoch())
309                     .count();
310         _log << "," << _maximumSetPoint;
311     }
312 
313     for (const auto& f : _fanInputs)
314     {
315         auto sensor = _mgr.getSensor(f);
316         ReadReturn r = sensor->read();
317         _cachedValuesByName[f] = r.value;
318         int64_t timeout = sensor->getTimeout();
319         tstamp then = r.updated;
320 
321         auto duration =
322             std::chrono::duration_cast<std::chrono::seconds>(now - then)
323                 .count();
324         auto period = std::chrono::seconds(timeout).count();
325         /*
326          * TODO(venture): We should check when these were last read.
327          * However, these are the fans, so if I'm not getting updated values
328          * for them... what should I do?
329          */
330         if (loggingEnabled)
331         {
332             _log << "," << r.value;
333         }
334 
335         // check if fan fail.
336         if (sensor->getFailed())
337         {
338             _failSafeSensors.insert(f);
339         }
340         else if (timeout != 0 && duration >= period)
341         {
342             _failSafeSensors.insert(f);
343         }
344         else
345         {
346             // Check if it's in there: remove it.
347             auto kt = _failSafeSensors.find(f);
348             if (kt != _failSafeSensors.end())
349             {
350                 _failSafeSensors.erase(kt);
351             }
352         }
353     }
354 
355     if (loggingEnabled)
356     {
357         for (const auto& t : _thermalInputs)
358         {
359             _log << "," << _cachedValuesByName[t];
360         }
361     }
362 
363     return;
364 }
365 
366 void DbusPidZone::updateSensors(void)
367 {
368     using namespace std::chrono;
369     /* margin and temp are stored as temp */
370     tstamp now = high_resolution_clock::now();
371 
372     for (const auto& t : _thermalInputs)
373     {
374         auto sensor = _mgr.getSensor(t);
375         ReadReturn r = sensor->read();
376         int64_t timeout = sensor->getTimeout();
377 
378         _cachedValuesByName[t] = r.value;
379         tstamp then = r.updated;
380 
381         auto duration = duration_cast<std::chrono::seconds>(now - then).count();
382         auto period = std::chrono::seconds(timeout).count();
383 
384         if (sensor->getFailed())
385         {
386             _failSafeSensors.insert(t);
387         }
388         else if (timeout != 0 && duration >= period)
389         {
390             // std::cerr << "Entering fail safe mode.\n";
391             _failSafeSensors.insert(t);
392         }
393         else
394         {
395             // Check if it's in there: remove it.
396             auto kt = _failSafeSensors.find(t);
397             if (kt != _failSafeSensors.end())
398             {
399                 _failSafeSensors.erase(kt);
400             }
401         }
402     }
403 
404     return;
405 }
406 
407 void DbusPidZone::initializeCache(void)
408 {
409     for (const auto& f : _fanInputs)
410     {
411         _cachedValuesByName[f] = 0;
412 
413         // Start all fans in fail-safe mode.
414         _failSafeSensors.insert(f);
415     }
416 
417     for (const auto& t : _thermalInputs)
418     {
419         _cachedValuesByName[t] = 0;
420 
421         // Start all sensors in fail-safe mode.
422         _failSafeSensors.insert(t);
423     }
424 }
425 
426 void DbusPidZone::dumpCache(void)
427 {
428     std::cerr << "Cache values now: \n";
429     for (const auto& [name, value] : _cachedValuesByName)
430     {
431         std::cerr << name << ": " << value << "\n";
432     }
433 }
434 
435 void DbusPidZone::processFans(void)
436 {
437     for (auto& p : _fans)
438     {
439         p->process();
440     }
441 
442     if (_redundantWrite)
443     {
444         // This is only needed once
445         _redundantWrite = false;
446     }
447 }
448 
449 void DbusPidZone::processThermals(void)
450 {
451     for (auto& p : _thermals)
452     {
453         p->process();
454     }
455 }
456 
457 Sensor* DbusPidZone::getSensor(const std::string& name)
458 {
459     return _mgr.getSensor(name);
460 }
461 
462 bool DbusPidZone::getRedundantWrite(void) const
463 {
464     return _redundantWrite;
465 }
466 
467 bool DbusPidZone::manual(bool value)
468 {
469     std::cerr << "manual: " << value << std::endl;
470     setManualMode(value);
471     return ModeObject::manual(value);
472 }
473 
474 bool DbusPidZone::failSafe() const
475 {
476     return getFailSafeMode();
477 }
478 
479 } // namespace pid_control
480