xref: /openbmc/phosphor-pid-control/pid/zone.cpp (revision c7ab57e9)
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 
35 using tstamp = std::chrono::high_resolution_clock::time_point;
36 using namespace std::literals::chrono_literals;
37 
38 double PIDZone::getMaxSetPointRequest(void) const
39 {
40     return _maximumSetPoint;
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::addSetPoint(double setpoint)
65 {
66     _SetPoints.push_back(setpoint);
67 }
68 
69 void PIDZone::addRPMCeiling(double ceiling)
70 {
71     _RPMCeilings.push_back(ceiling);
72 }
73 
74 void PIDZone::clearRPMCeilings(void)
75 {
76     _RPMCeilings.clear();
77 }
78 
79 void PIDZone::clearSetPoints(void)
80 {
81     _SetPoints.clear();
82 }
83 
84 double PIDZone::getFailSafePercent(void) const
85 {
86     return _failSafePercent;
87 }
88 
89 double PIDZone::getMinThermalSetpoint(void) const
90 {
91     return _minThermalOutputSetPt;
92 }
93 
94 void PIDZone::addFanPID(std::unique_ptr<Controller> pid)
95 {
96     _fans.push_back(std::move(pid));
97 }
98 
99 void PIDZone::addThermalPID(std::unique_ptr<Controller> pid)
100 {
101     _thermals.push_back(std::move(pid));
102 }
103 
104 double PIDZone::getCachedValue(const std::string& name)
105 {
106     return _cachedValuesByName.at(name);
107 }
108 
109 void PIDZone::addFanInput(const std::string& fan)
110 {
111     _fanInputs.push_back(fan);
112 }
113 
114 void PIDZone::addThermalInput(const std::string& therm)
115 {
116     _thermalInputs.push_back(therm);
117 }
118 
119 void PIDZone::determineMaxSetPointRequest(void)
120 {
121     double max = 0;
122     std::vector<double>::iterator result;
123 
124     if (_SetPoints.size() > 0)
125     {
126         result = std::max_element(_SetPoints.begin(), _SetPoints.end());
127         max = *result;
128     }
129 
130     if (_RPMCeilings.size() > 0)
131     {
132         result = std::min_element(_RPMCeilings.begin(), _RPMCeilings.end());
133         max = std::min(max, *result);
134     }
135 
136     /*
137      * If the maximum RPM setpoint output is below the minimum RPM
138      * setpoint, set it to the minimum.
139      */
140     max = std::max(getMinThermalSetpoint(), max);
141 
142     if (tuningEnabled)
143     {
144         /*
145          * We received no setpoints from thermal sensors.
146          * This is a case experienced during tuning where they only specify
147          * fan sensors and one large fan PID for all the fans.
148          */
149         static constexpr auto setpointpath = "/etc/thermal.d/setpoint";
150         try
151         {
152             std::ifstream ifs;
153             ifs.open(setpointpath);
154             if (ifs.good())
155             {
156                 int value;
157                 ifs >> value;
158 
159                 /* expecting RPM setpoint, not pwm% */
160                 max = static_cast<double>(value);
161             }
162         }
163         catch (const std::exception& e)
164         {
165             /* This exception is uninteresting. */
166             std::cerr << "Unable to read from '" << setpointpath << "'\n";
167         }
168     }
169 
170     _maximumSetPoint = max;
171     return;
172 }
173 
174 void PIDZone::initializeLog(void)
175 {
176     /* Print header for log file:
177      * epoch_ms,setpt,fan1,fan2,fanN,sensor1,sensor2,sensorN,failsafe
178      */
179 
180     _log << "epoch_ms,setpt";
181 
182     for (const auto& f : _fanInputs)
183     {
184         _log << "," << f;
185     }
186     for (const auto& t : _thermalInputs)
187     {
188         _log << "," << t;
189     }
190     _log << ",failsafe";
191     _log << std::endl;
192 
193     return;
194 }
195 
196 std::ofstream& PIDZone::getLogHandle(void)
197 {
198     return _log;
199 }
200 
201 /*
202  * TODO(venture) This is effectively updating the cache and should check if the
203  * values they're using to update it are new or old, or whatnot.  For instance,
204  * if we haven't heard from the host in X time we need to detect this failure.
205  *
206  * I haven't decided if the Sensor should have a lastUpdated method or whether
207  * that should be for the ReadInterface or etc...
208  */
209 
210 /**
211  * We want the PID loop to run with values cached, so this will get all the
212  * fan tachs for the loop.
213  */
214 void PIDZone::updateFanTelemetry(void)
215 {
216     /* TODO(venture): Should I just make _log point to /dev/null when logging
217      * is disabled?  I think it's a waste to try and log things even if the
218      * data is just being dropped though.
219      */
220     tstamp now = std::chrono::high_resolution_clock::now();
221     if (loggingEnabled)
222     {
223         _log << std::chrono::duration_cast<std::chrono::milliseconds>(
224                     now.time_since_epoch())
225                     .count();
226         _log << "," << _maximumSetPoint;
227     }
228 
229     for (const auto& f : _fanInputs)
230     {
231         auto sensor = _mgr.getSensor(f);
232         ReadReturn r = sensor->read();
233         _cachedValuesByName[f] = r.value;
234         int64_t timeout = sensor->getTimeout();
235         tstamp then = r.updated;
236 
237         auto duration =
238             std::chrono::duration_cast<std::chrono::seconds>(now - then)
239                 .count();
240         auto period = std::chrono::seconds(timeout).count();
241         /*
242          * TODO(venture): We should check when these were last read.
243          * However, these are the fans, so if I'm not getting updated values
244          * for them... what should I do?
245          */
246         if (loggingEnabled)
247         {
248             _log << "," << r.value;
249         }
250 
251         // check if fan fail.
252         if (sensor->getFailed())
253         {
254             _failSafeSensors.insert(f);
255         }
256         else if (timeout != 0 && duration >= period)
257         {
258             _failSafeSensors.insert(f);
259         }
260         else
261         {
262             // Check if it's in there: remove it.
263             auto kt = _failSafeSensors.find(f);
264             if (kt != _failSafeSensors.end())
265             {
266                 _failSafeSensors.erase(kt);
267             }
268         }
269     }
270 
271     if (loggingEnabled)
272     {
273         for (const auto& t : _thermalInputs)
274         {
275             _log << "," << _cachedValuesByName[t];
276         }
277     }
278 
279     return;
280 }
281 
282 void PIDZone::updateSensors(void)
283 {
284     using namespace std::chrono;
285     /* margin and temp are stored as temp */
286     tstamp now = high_resolution_clock::now();
287 
288     for (const auto& t : _thermalInputs)
289     {
290         auto sensor = _mgr.getSensor(t);
291         ReadReturn r = sensor->read();
292         int64_t timeout = sensor->getTimeout();
293 
294         _cachedValuesByName[t] = r.value;
295         tstamp then = r.updated;
296 
297         auto duration = duration_cast<std::chrono::seconds>(now - then).count();
298         auto period = std::chrono::seconds(timeout).count();
299 
300         if (sensor->getFailed())
301         {
302             _failSafeSensors.insert(t);
303         }
304         else if (timeout != 0 && duration >= period)
305         {
306             // std::cerr << "Entering fail safe mode.\n";
307             _failSafeSensors.insert(t);
308         }
309         else
310         {
311             // Check if it's in there: remove it.
312             auto kt = _failSafeSensors.find(t);
313             if (kt != _failSafeSensors.end())
314             {
315                 _failSafeSensors.erase(kt);
316             }
317         }
318     }
319 
320     return;
321 }
322 
323 void PIDZone::initializeCache(void)
324 {
325     for (const auto& f : _fanInputs)
326     {
327         _cachedValuesByName[f] = 0;
328 
329         // Start all fans in fail-safe mode.
330         _failSafeSensors.insert(f);
331     }
332 
333     for (const auto& t : _thermalInputs)
334     {
335         _cachedValuesByName[t] = 0;
336 
337         // Start all sensors in fail-safe mode.
338         _failSafeSensors.insert(t);
339     }
340 }
341 
342 void PIDZone::dumpCache(void)
343 {
344     std::cerr << "Cache values now: \n";
345     for (const auto& k : _cachedValuesByName)
346     {
347         std::cerr << k.first << ": " << k.second << "\n";
348     }
349 }
350 
351 void PIDZone::processFans(void)
352 {
353     for (auto& p : _fans)
354     {
355         p->process();
356     }
357 }
358 
359 void PIDZone::processThermals(void)
360 {
361     for (auto& p : _thermals)
362     {
363         p->process();
364     }
365 }
366 
367 Sensor* PIDZone::getSensor(const std::string& name)
368 {
369     return _mgr.getSensor(name);
370 }
371 
372 bool PIDZone::manual(bool value)
373 {
374     std::cerr << "manual: " << value << std::endl;
375     setManualMode(value);
376     return ModeObject::manual(value);
377 }
378 
379 bool PIDZone::failSafe() const
380 {
381     return getFailSafeMode();
382 }
383