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