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 <algorithm>
17 #include <phosphor-logging/log.hpp>
18 #include "fan.hpp"
19 #include "types.hpp"
20 #include "utility.hpp"
21 
22 namespace phosphor
23 {
24 namespace fan
25 {
26 namespace monitor
27 {
28 
29 using namespace phosphor::logging;
30 using TimerType = phosphor::fan::util::Timer::TimerType;
31 
32 constexpr auto INVENTORY_PATH = "/xyz/openbmc_project/inventory";
33 constexpr auto INVENTORY_INTF = "xyz.openbmc_project.Inventory.Manager";
34 
35 constexpr auto FUNCTIONAL_PROPERTY = "Functional";
36 constexpr auto OPERATIONAL_STATUS_INTF  =
37     "xyz.openbmc_project.State.Decorator.OperationalStatus";
38 
39 
40 Fan::Fan(sdbusplus::bus::bus& bus,
41          phosphor::fan::event::EventPtr&  events,
42          const FanDefinition& def) :
43     _bus(bus),
44     _name(std::get<fanNameField>(def)),
45     _deviation(std::get<fanDeviationField>(def)),
46     _numSensorFailsForNonFunc(std::get<numSensorFailsForNonfuncField>(def))
47 {
48     auto& sensors = std::get<sensorListField>(def);
49 
50     for (auto& s : sensors)
51     {
52         _sensors.emplace_back(
53             std::make_unique<TachSensor>(bus,
54                                          *this,
55                                          std::get<sensorNameField>(s),
56                                          std::get<hasTargetField>(s),
57                                          std::get<timeoutField>(def),
58                                          events));
59     }
60 
61     //Start from a known state of functional
62     updateInventory(true);
63 
64     //The TachSensors will now have already read the input
65     //and target values, so check them.
66     tachChanged();
67 }
68 
69 
70 void Fan::tachChanged()
71 {
72     for (auto& s : _sensors)
73     {
74         tachChanged(*s);
75     }
76 }
77 
78 
79 void Fan::tachChanged(TachSensor& sensor)
80 {
81     auto& timer = sensor.getTimer();
82     auto running = timer.running();
83 
84     //If this sensor is out of range at this moment, start
85     //its timer, at the end of which the inventory
86     //for the fan may get updated to not functional.
87 
88     //If this sensor is OK, put everything back into a good state.
89 
90     if (outOfRange(sensor))
91     {
92         if (sensor.functional() && !running)
93         {
94             timer.start(sensor.getTimeout(), TimerType::oneshot);
95         }
96     }
97     else
98     {
99         if (!sensor.functional())
100         {
101             sensor.setFunctional(true);
102         }
103 
104         if (running)
105         {
106             timer.stop();
107         }
108 
109         //If the fan was nonfunctional and enough sensors are now OK,
110         //the fan can go back to functional
111         if (!_functional && !tooManySensorsNonfunctional())
112         {
113             log<level::INFO>("Setting a fan back to functional",
114                              entry("FAN=%s", _name.c_str()));
115 
116             updateInventory(true);
117         }
118     }
119 }
120 
121 
122 uint64_t Fan::getTargetSpeed(const TachSensor& sensor)
123 {
124     uint64_t target = 0;
125 
126     if (sensor.hasTarget())
127     {
128         target = sensor.getTarget();
129     }
130     else
131     {
132         //The sensor doesn't support a target,
133         //so get it from another sensor.
134         auto s = std::find_if(_sensors.begin(), _sensors.end(),
135                               [](const auto& s)
136                               {
137                                   return s->hasTarget();
138                               });
139 
140         if (s != _sensors.end())
141         {
142             target = (*s)->getTarget();
143         }
144     }
145 
146     return target;
147 }
148 
149 
150 bool Fan::tooManySensorsNonfunctional()
151 {
152     size_t numFailed =  std::count_if(_sensors.begin(), _sensors.end(),
153                                       [](const auto& s)
154                                       {
155                                           return !s->functional();
156                                       });
157 
158     return (numFailed >= _numSensorFailsForNonFunc);
159 }
160 
161 
162 bool Fan::outOfRange(const TachSensor& sensor)
163 {
164     auto actual = static_cast<uint64_t>(sensor.getInput());
165     auto target = getTargetSpeed(sensor);
166 
167     uint64_t min = target * (100 - _deviation) / 100;
168     uint64_t max = target * (100 + _deviation) / 100;
169 
170     if ((actual < min) || (actual > max))
171     {
172         return true;
173     }
174 
175     return false;
176 }
177 
178 
179 void Fan::timerExpired(TachSensor& sensor)
180 {
181     sensor.setFunctional(false);
182 
183     //If the fan is currently functional, but too many
184     //contained sensors are now nonfunctional, update
185     //the whole fan nonfunctional.
186 
187     if (_functional && tooManySensorsNonfunctional())
188     {
189         log<level::ERR>("Setting a fan to nonfunctional",
190                 entry("FAN=%s", _name.c_str()),
191                 entry("TACH_SENSOR=%s", sensor.name().c_str()),
192                 entry("ACTUAL_SPEED=%lld", sensor.getInput()),
193                 entry("TARGET_SPEED=%lld", getTargetSpeed(sensor)));
194 
195         updateInventory(false);
196     }
197 }
198 
199 
200 void Fan::updateInventory(bool functional)
201 {
202     ObjectMap objectMap = getObjectMap(functional);
203     std::string service;
204 
205     service = phosphor::fan::util::getInvService(_bus);
206 
207     auto msg = _bus.new_method_call(service.c_str(),
208                                    INVENTORY_PATH,
209                                    INVENTORY_INTF,
210                                    "Notify");
211 
212     msg.append(std::move(objectMap));
213     auto response = _bus.call(msg);
214     if (response.is_method_error())
215     {
216         log<level::ERR>("Error in Notify call to update inventory");
217         return;
218     }
219 
220     //This will always track the current state of the inventory.
221     _functional = functional;
222 }
223 
224 
225 Fan::ObjectMap Fan::getObjectMap(bool functional)
226 {
227     ObjectMap objectMap;
228     InterfaceMap interfaceMap;
229     PropertyMap propertyMap;
230 
231     propertyMap.emplace(FUNCTIONAL_PROPERTY, functional);
232     interfaceMap.emplace(OPERATIONAL_STATUS_INTF, std::move(propertyMap));
233     objectMap.emplace(_name, std::move(interfaceMap));
234 
235     return objectMap;
236 }
237 
238 
239 }
240 }
241 }
242