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