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