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