xref: /openbmc/phosphor-pid-control/pid/zone.cpp (revision 1fe08952)
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::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(double setpoint)
65 {
66     _RPMSetPoints.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::clearRPMSetPoints(void)
80 {
81     _RPMSetPoints.clear();
82 }
83 
84 double PIDZone::getFailSafePercent(void) const
85 {
86     return _failSafePercent;
87 }
88 
89 double PIDZone::getMinThermalRPMSetpoint(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::determineMaxRPMRequest(void)
120 {
121     double max = 0;
122     std::vector<double>::iterator result;
123 
124     if (_RPMSetPoints.size() > 0)
125     {
126         result = std::max_element(_RPMSetPoints.begin(), _RPMSetPoints.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(getMinThermalRPMSetpoint(), 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     _maximumRPMSetPt = 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     if (loggingEnabled)
221     {
222         tstamp now = std::chrono::high_resolution_clock::now();
223         _log << std::chrono::duration_cast<std::chrono::milliseconds>(
224                     now.time_since_epoch())
225                     .count();
226         _log << "," << _maximumRPMSetPt;
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 
235         /*
236          * TODO(venture): We should check when these were last read.
237          * However, these are the fans, so if I'm not getting updated values
238          * for them... what should I do?
239          */
240         if (loggingEnabled)
241         {
242             _log << "," << r.value;
243         }
244     }
245 
246     if (loggingEnabled)
247     {
248         for (const auto& t : _thermalInputs)
249         {
250             _log << "," << _cachedValuesByName[t];
251         }
252     }
253 
254     return;
255 }
256 
257 void PIDZone::updateSensors(void)
258 {
259     using namespace std::chrono;
260     /* margin and temp are stored as temp */
261     tstamp now = high_resolution_clock::now();
262 
263     for (const auto& t : _thermalInputs)
264     {
265         auto sensor = _mgr.getSensor(t);
266         ReadReturn r = sensor->read();
267         int64_t timeout = sensor->getTimeout();
268 
269         _cachedValuesByName[t] = r.value;
270         tstamp then = r.updated;
271 
272         auto duration = duration_cast<std::chrono::seconds>(now - then).count();
273         auto period = std::chrono::seconds(timeout).count();
274 
275         if (sensor->getFailed())
276         {
277             _failSafeSensors.insert(t);
278         }
279         else if (timeout != 0 && duration >= period)
280         {
281             // std::cerr << "Entering fail safe mode.\n";
282             _failSafeSensors.insert(t);
283         }
284         else
285         {
286             // Check if it's in there: remove it.
287             auto kt = _failSafeSensors.find(t);
288             if (kt != _failSafeSensors.end())
289             {
290                 _failSafeSensors.erase(kt);
291             }
292         }
293     }
294 
295     return;
296 }
297 
298 void PIDZone::initializeCache(void)
299 {
300     for (const auto& f : _fanInputs)
301     {
302         _cachedValuesByName[f] = 0;
303     }
304 
305     for (const auto& t : _thermalInputs)
306     {
307         _cachedValuesByName[t] = 0;
308 
309         // Start all sensors in fail-safe mode.
310         _failSafeSensors.insert(t);
311     }
312 }
313 
314 void PIDZone::dumpCache(void)
315 {
316     std::cerr << "Cache values now: \n";
317     for (const auto& k : _cachedValuesByName)
318     {
319         std::cerr << k.first << ": " << k.second << "\n";
320     }
321 }
322 
323 void PIDZone::processFans(void)
324 {
325     for (auto& p : _fans)
326     {
327         p->process();
328     }
329 }
330 
331 void PIDZone::processThermals(void)
332 {
333     for (auto& p : _thermals)
334     {
335         p->process();
336     }
337 }
338 
339 Sensor* PIDZone::getSensor(const std::string& name)
340 {
341     return _mgr.getSensor(name);
342 }
343 
344 bool PIDZone::manual(bool value)
345 {
346     std::cerr << "manual: " << value << std::endl;
347     setManualMode(value);
348     return ModeObject::manual(value);
349 }
350 
351 bool PIDZone::failSafe() const
352 {
353     return getFailSafeMode();
354 }
355