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