1 /**
2  * Copyright © 2017 IBM Corporation
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 "fan.hpp"
17 
18 #include "sdbusplus.hpp"
19 #include "types.hpp"
20 #include "utility.hpp"
21 
22 #include <phosphor-logging/log.hpp>
23 
24 #include <algorithm>
25 
26 namespace phosphor
27 {
28 namespace fan
29 {
30 namespace monitor
31 {
32 
33 using namespace phosphor::logging;
34 
35 Fan::Fan(Mode mode, sdbusplus::bus::bus& bus, const sdeventplus::Event& event,
36          std::unique_ptr<trust::Manager>& trust, const FanDefinition& def) :
37     _bus(bus),
38     _name(std::get<fanNameField>(def)),
39     _deviation(std::get<fanDeviationField>(def)),
40     _numSensorFailsForNonFunc(std::get<numSensorFailsForNonfuncField>(def)),
41     _trustManager(trust)
42 {
43     // Setup tach sensors for monitoring
44     auto& sensors = std::get<sensorListField>(def);
45     for (auto& s : sensors)
46     {
47         try
48         {
49             _sensors.emplace_back(std::make_shared<TachSensor>(
50                 mode, bus, *this, std::get<sensorNameField>(s),
51                 std::get<hasTargetField>(s), std::get<funcDelay>(def),
52                 std::get<targetInterfaceField>(s), std::get<factorField>(s),
53                 std::get<offsetField>(s), std::get<timeoutField>(def), event));
54 
55             _trustManager->registerSensor(_sensors.back());
56         }
57         catch (InvalidSensorError& e)
58         {}
59     }
60 
61     // Start from a known state of functional
62     updateInventory(true);
63 
64     // Check current tach state when entering monitor mode
65     if (mode != Mode::init)
66     {
67         // The TachSensors will now have already read the input
68         // and target values, so check them.
69         tachChanged();
70     }
71 }
72 
73 void Fan::tachChanged()
74 {
75     for (auto& s : _sensors)
76     {
77         tachChanged(*s);
78     }
79 }
80 
81 void Fan::tachChanged(TachSensor& sensor)
82 {
83     if (_trustManager->active())
84     {
85         if (!_trustManager->checkTrust(sensor))
86         {
87             return;
88         }
89     }
90 
91     // If this sensor is out of range at this moment, start
92     // its timer, at the end of which the inventory
93     // for the fan may get updated to not functional.
94 
95     // If this sensor is OK, put everything back into a good state.
96 
97     if (outOfRange(sensor))
98     {
99         if (sensor.functional())
100         {
101             // Start nonfunctional timer if not already running
102             sensor.startTimer(TimerMode::nonfunc);
103         }
104     }
105     else
106     {
107         if (sensor.functional())
108         {
109             sensor.stopTimer();
110         }
111         else
112         {
113             // Start functional timer if not already running
114             sensor.startTimer(TimerMode::func);
115         }
116     }
117 }
118 
119 uint64_t Fan::findTargetSpeed()
120 {
121     uint64_t target = 0;
122     // The sensor doesn't support a target,
123     // so get it from another sensor.
124     auto s = std::find_if(_sensors.begin(), _sensors.end(),
125                           [](const auto& s) { return s->hasTarget(); });
126 
127     if (s != _sensors.end())
128     {
129         target = (*s)->getTarget();
130     }
131 
132     return target;
133 }
134 
135 bool Fan::tooManySensorsNonfunctional()
136 {
137     size_t numFailed =
138         std::count_if(_sensors.begin(), _sensors.end(),
139                       [](const auto& s) { return !s->functional(); });
140 
141     return (numFailed >= _numSensorFailsForNonFunc);
142 }
143 
144 bool Fan::outOfRange(const TachSensor& sensor)
145 {
146     auto actual = static_cast<uint64_t>(sensor.getInput());
147     auto target = sensor.getTarget();
148     auto factor = sensor.getFactor();
149     auto offset = sensor.getOffset();
150 
151     uint64_t min = target * (100 - _deviation) / 100;
152     uint64_t max = target * (100 + _deviation) / 100;
153 
154     // TODO: openbmc/openbmc#2937 enhance this function
155     // either by making it virtual, or by predefining different
156     // outOfRange ops and selecting by yaml config
157     min = min * factor + offset;
158     max = max * factor + offset;
159     if ((actual < min) || (actual > max))
160     {
161         return true;
162     }
163 
164     return false;
165 }
166 
167 void Fan::timerExpired(TachSensor& sensor)
168 {
169     sensor.setFunctional(!sensor.functional());
170 
171     // If the fan was nonfunctional and enough sensors are now OK,
172     // the fan can go back to functional
173     if (!_functional && !tooManySensorsNonfunctional())
174     {
175         log<level::INFO>("Setting a fan back to functional",
176                          entry("FAN=%s", _name.c_str()));
177 
178         updateInventory(true);
179     }
180 
181     // If the fan is currently functional, but too many
182     // contained sensors are now nonfunctional, update
183     // the whole fan nonfunctional.
184     if (_functional && tooManySensorsNonfunctional())
185     {
186         log<level::ERR>("Setting a fan to nonfunctional",
187                         entry("FAN=%s", _name.c_str()),
188                         entry("TACH_SENSOR=%s", sensor.name().c_str()),
189                         entry("ACTUAL_SPEED=%lld", sensor.getInput()),
190                         entry("TARGET_SPEED=%lld", sensor.getTarget()));
191 
192         updateInventory(false);
193     }
194 }
195 
196 void Fan::updateInventory(bool functional)
197 {
198     auto objectMap =
199         util::getObjMap<bool>(_name, util::OPERATIONAL_STATUS_INTF,
200                               util::FUNCTIONAL_PROPERTY, functional);
201     auto response = util::SDBusPlus::lookupAndCallMethod(
202         _bus, util::INVENTORY_PATH, util::INVENTORY_INTF, "Notify", objectMap);
203     if (response.is_method_error())
204     {
205         log<level::ERR>("Error in Notify call to update inventory");
206         return;
207     }
208 
209     // This will always track the current state of the inventory.
210     _functional = functional;
211 }
212 
213 } // namespace monitor
214 } // namespace fan
215 } // namespace phosphor
216