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