xref: /openbmc/phosphor-pid-control/pid/fancontroller.cpp (revision f8b6e55147148c3cfb42327ff267197a460b411c)
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 #include "fancontroller.hpp"
18 
19 #include "ec/pid.hpp"
20 #include "fan.hpp"
21 #include "pidcontroller.hpp"
22 #include "tuning.hpp"
23 #include "util.hpp"
24 #include "zone_interface.hpp"
25 
26 #include <algorithm>
27 #include <cmath>
28 #include <cstdint>
29 #include <exception>
30 #include <iostream>
31 #include <map>
32 #include <memory>
33 #include <string>
34 #include <utility>
35 #include <vector>
36 
37 namespace pid_control
38 {
39 
createFanPid(ZoneInterface * owner,const std::string & id,const std::vector<std::string> & inputs,const ec::pidinfo & initial)40 std::unique_ptr<PIDController> FanController::createFanPid(
41     ZoneInterface* owner, const std::string& id,
42     const std::vector<std::string>& inputs, const ec::pidinfo& initial)
43 {
44     if (inputs.size() == 0)
45     {
46         return nullptr;
47     }
48     auto fan = std::make_unique<FanController>(id, inputs, owner);
49     ec::pid_info_t* info = fan->getPIDInfo();
50 
51     initializePIDStruct(info, initial);
52 
53     return fan;
54 }
55 
inputProc(void)56 double FanController::inputProc(void)
57 {
58     double value = 0.0;
59     std::vector<double> values;
60     std::vector<double>::iterator result;
61 
62     try
63     {
64         for (const auto& name : _inputs)
65         {
66             // Read the unscaled value, to correctly recover the RPM
67             value = _owner->getCachedValues(name).unscaled;
68 
69             /* If we have a fan we can't read, its value will be 0 for at least
70              * some boards, while others... the fan will drop off dbus (if
71              * that's how it's being read and in that case its value will never
72              * be updated anymore, which is relatively harmless, except, when
73              * something tries to read its value through IPMI, and can't, they
74              * sort of have to guess -- all the other fans are reporting, why
75              * not this one?  Maybe it's unable to be read, so it's "bad."
76              */
77             if (!(std::isfinite(value)))
78             {
79                 continue;
80             }
81             if (value <= 0.0)
82             {
83                 continue;
84             }
85 
86             values.push_back(value);
87         }
88     }
89     catch (const std::exception& e)
90     {
91         std::cerr << "exception on inputProc.\n";
92         throw;
93     }
94 
95     /* Reset the value from the above loop. */
96     value = 0.0;
97     if (values.size() > 0)
98     {
99         /* the fan PID algorithm was unstable with average, and seemed to work
100          * better with minimum.  I had considered making this choice a variable
101          * in the configuration, and it's a nice-to-have..
102          */
103         result = std::min_element(values.begin(), values.end());
104         value = *result;
105     }
106 
107     return value;
108 }
109 
setptProc(void)110 double FanController::setptProc(void)
111 {
112     double maxRPM = _owner->getMaxSetPointRequest();
113 
114     // store for reference, and check if more or less.
115     double prev = getSetpoint();
116 
117     if (maxRPM > prev)
118     {
119         setFanDirection(FanSpeedDirection::UP);
120     }
121     else if (prev > maxRPM)
122     {
123         setFanDirection(FanSpeedDirection::DOWN);
124     }
125     else
126     {
127         setFanDirection(FanSpeedDirection::NEUTRAL);
128     }
129 
130     setSetpoint(maxRPM);
131 
132     return (maxRPM);
133 }
134 
outputProc(double value)135 void FanController::outputProc(double value)
136 {
137     double percent = value;
138 
139     /* If doing tuning, don't go into failsafe mode. */
140     if (!tuningEnabled)
141     {
142         bool failsafeCurrState = _owner->getFailSafeMode();
143 
144         // Note when failsafe state transitions happen
145         if (failsafePrevState != failsafeCurrState)
146         {
147             failsafePrevState = failsafeCurrState;
148             failsafeTransition = true;
149         }
150 
151         if (failsafeCurrState)
152         {
153             double failsafePercent = _owner->getFailSafePercent();
154 
155 #ifdef STRICT_FAILSAFE_PWM
156             // Unconditionally replace the computed PWM with the
157             // failsafe PWM if STRICT_FAILSAFE_PWM is defined.
158             percent = failsafePercent;
159 #else
160             // Ensure PWM is never lower than the failsafe PWM.
161             // The computed PWM is still allowed to rise higher than
162             // failsafe PWM if STRICT_FAILSAFE_PWM is NOT defined.
163             // This is the default behavior.
164             if (percent < failsafePercent)
165             {
166                 percent = failsafePercent;
167             }
168 #endif
169         }
170 
171         // Always print if debug enabled
172         if (debugEnabled)
173         {
174             std::cerr << "Zone " << _owner->getZoneID() << " fans, "
175                       << (failsafeCurrState ? "failsafe" : "normal")
176                       << " mode, output pwm: " << percent << "\n";
177         }
178         else
179         {
180             // Only print once per transition when not debugging
181             if (failsafeTransition)
182             {
183                 failsafeTransition = false;
184                 std::cerr << "Zone " << _owner->getZoneID() << " fans, "
185                           << (failsafeCurrState ? "entering failsafe"
186                                                 : "returning to normal")
187                           << " mode, output pwm: " << percent << "\n";
188 
189                 std::map<std::string, std::pair<std::string, double>>
190                     failSensorList = _owner->getFailSafeSensors();
191                 for (const auto& it : failSensorList)
192                 {
193                     std::cerr << "Fail sensor: " << it.first
194                               << ", reason: " << it.second.first << "\n";
195                 }
196             }
197         }
198     }
199     else
200     {
201         if (debugEnabled)
202         {
203             std::cerr << "Zone " << _owner->getZoneID()
204                       << " fans, tuning mode, bypassing failsafe, output pwm: "
205                       << percent << "\n";
206         }
207     }
208 
209     // value and kFanFailSafeDutyCycle are 10 for 10% so let's fix that.
210     percent /= 100.0;
211 
212     // PidSensorMap for writing.
213     for (const auto& it : _inputs)
214     {
215         auto sensor = _owner->getSensor(it);
216         auto redundantWrite = _owner->getRedundantWrite();
217         int64_t rawWritten = -1;
218         sensor->write(percent, redundantWrite, &rawWritten);
219 
220         // The outputCache will be used later,
221         // to store a record of the PWM commanded,
222         // so that this information can be included during logging.
223         auto unscaledWritten = static_cast<double>(rawWritten);
224         _owner->setOutputCache(sensor->getName(), {percent, unscaledWritten});
225     }
226 
227     return;
228 }
229 
~FanController()230 FanController::~FanController()
231 {
232 #ifdef OFFLINE_FAILSAFE_PWM
233     double percent = _owner->getFailSafePercent();
234     if (debugEnabled)
235     {
236         std::cerr << "Zone " << _owner->getZoneID()
237                   << " offline fans output pwm: " << percent << "\n";
238     }
239 
240     // value and kFanFailSafeDutyCycle are 10 for 10% so let's fix that.
241     percent /= 100.0;
242 
243     // PidSensorMap for writing.
244     for (const auto& it : _inputs)
245     {
246         auto sensor = _owner->getSensor(it);
247         auto redundantWrite = _owner->getRedundantWrite();
248         int64_t rawWritten;
249         sensor->write(percent, redundantWrite, &rawWritten);
250 
251         // The outputCache will be used later,
252         // to store a record of the PWM commanded,
253         // so that this information can be included during logging.
254         auto unscaledWritten = static_cast<double>(rawWritten);
255         _owner->setOutputCache(sensor->getName(), {percent, unscaledWritten});
256     }
257 #endif
258 }
259 
260 } // namespace pid_control
261