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 <chrono>
17 #include <phosphor-logging/log.hpp>
18 #include <phosphor-logging/elog.hpp>
19 #include <phosphor-logging/elog-errors.hpp>
20 #include <xyz/openbmc_project/Common/error.hpp>
21 #include "zone.hpp"
22 #include "utility.hpp"
23 #include "sdbusplus.hpp"
24 
25 namespace phosphor
26 {
27 namespace fan
28 {
29 namespace control
30 {
31 
32 using namespace std::chrono;
33 using namespace phosphor::fan;
34 using namespace phosphor::logging;
35 using InternalFailure = sdbusplus::xyz::openbmc_project::Common::
36                              Error::InternalFailure;
37 
38 Zone::Zone(Mode mode,
39            sdbusplus::bus::bus& bus,
40            phosphor::fan::event::EventPtr& events,
41            const ZoneDefinition& def) :
42     _bus(bus),
43     _fullSpeed(std::get<fullSpeedPos>(def)),
44     _zoneNum(std::get<zoneNumPos>(def)),
45     _defFloorSpeed(std::get<floorSpeedPos>(def)),
46     _defCeilingSpeed(std::get<fullSpeedPos>(def)),
47     _incDelay(std::get<incDelayPos>(def)),
48     _decInterval(std::get<decIntervalPos>(def)),
49     _incTimer(events, [this](){ this->incTimerExpired(); }),
50     _decTimer(events, [this](){ this->decTimerExpired(); }),
51     _sdEvents(events)
52 {
53     auto& fanDefs = std::get<fanListPos>(def);
54 
55     for (auto& def : fanDefs)
56     {
57         _fans.emplace_back(std::make_unique<Fan>(bus, def));
58     }
59 
60     // Do not enable set speed events when in init mode
61     if (mode != Mode::init)
62     {
63         // Setup signal trigger for set speed events
64         for (auto& event : std::get<setSpeedEventsPos>(def))
65         {
66             initEvent(event);
67         }
68         // Start timer for fan speed decreases
69         if (!_decTimer.running() && _decInterval != seconds::zero())
70         {
71             _decTimer.start(_decInterval,
72                             util::Timer::TimerType::repeating);
73         }
74     }
75 }
76 
77 void Zone::setSpeed(uint64_t speed)
78 {
79     if (_isActive)
80     {
81         _targetSpeed = speed;
82         for (auto& fan : _fans)
83         {
84             fan->setSpeed(_targetSpeed);
85         }
86     }
87 }
88 
89 void Zone::setFullSpeed()
90 {
91     if (_fullSpeed != 0)
92     {
93         _targetSpeed = _fullSpeed;
94         for (auto& fan : _fans)
95         {
96             fan->setSpeed(_targetSpeed);
97         }
98     }
99 }
100 
101 void Zone::setActiveAllow(const Group* group, bool isActiveAllow)
102 {
103     _active[*(group)] = isActiveAllow;
104     if (!isActiveAllow)
105     {
106         _isActive = false;
107     }
108     else
109     {
110         // Check all entries are set to allow control active
111         auto actPred = [](auto const& entry) {return entry.second;};
112         _isActive = std::all_of(_active.begin(),
113                                 _active.end(),
114                                 actPred);
115     }
116 }
117 
118 void Zone::setFloor(uint64_t speed)
119 {
120     _floorSpeed = speed;
121     // Floor speed above target, update target to floor speed
122     if (_targetSpeed < _floorSpeed)
123     {
124         requestSpeedIncrease(_floorSpeed - _targetSpeed);
125     }
126 }
127 
128 void Zone::requestSpeedIncrease(uint64_t targetDelta)
129 {
130     // Only increase speed when delta is higher than
131     // the current increase delta for the zone and currently under ceiling
132     if (targetDelta > _incSpeedDelta &&
133         _targetSpeed < _ceilingSpeed)
134     {
135         auto requestTarget = getRequestSpeedBase();
136         requestTarget = (targetDelta - _incSpeedDelta) + requestTarget;
137         _incSpeedDelta = targetDelta;
138         // Target speed can not go above a defined ceiling speed
139         if (requestTarget > _ceilingSpeed)
140         {
141             requestTarget = _ceilingSpeed;
142         }
143         // Cancel current timer countdown
144         if (_incTimer.running())
145         {
146             _incTimer.stop();
147         }
148         setSpeed(requestTarget);
149         // Start timer countdown for fan speed increase
150         _incTimer.start(_incDelay,
151                         util::Timer::TimerType::oneshot);
152     }
153 }
154 
155 void Zone::incTimerExpired()
156 {
157     // Clear increase delta when timer expires allowing additional speed
158     // increase requests or speed decreases to occur
159     _incSpeedDelta = 0;
160 }
161 
162 void Zone::requestSpeedDecrease(uint64_t targetDelta)
163 {
164     // Only decrease the lowest target delta requested
165     if (_decSpeedDelta == 0 || targetDelta < _decSpeedDelta)
166     {
167         _decSpeedDelta = targetDelta;
168     }
169 }
170 
171 void Zone::decTimerExpired()
172 {
173     // Only decrease speeds when no requested increases exist and
174     // the increase timer is not running (i.e. not in the middle of increasing)
175     if (_incSpeedDelta == 0 && !_incTimer.running())
176     {
177         auto requestTarget = getRequestSpeedBase();
178         // Target speed can not go below the defined floor speed
179         if ((requestTarget < _decSpeedDelta) ||
180             (requestTarget - _decSpeedDelta < _floorSpeed))
181         {
182             requestTarget = _floorSpeed;
183         }
184         else
185         {
186             requestTarget = requestTarget - _decSpeedDelta;
187         }
188         setSpeed(requestTarget);
189     }
190     // Clear decrease delta when timer expires
191     _decSpeedDelta = 0;
192     // Decrease timer is restarted since its repeating
193 }
194 
195 void Zone::initEvent(const SetSpeedEvent& event)
196 {
197     // Get the current value for each property
198     for (auto& group : std::get<groupPos>(event))
199     {
200         try
201         {
202             refreshProperty(_bus,
203                             group.first,
204                             std::get<intfPos>(group.second),
205                             std::get<propPos>(group.second));
206         }
207         catch (const InternalFailure& ife)
208         {
209             log<level::INFO>(
210                 "Unable to find property",
211                 entry("PATH=%s", group.first.c_str()),
212                 entry("INTERFACE=%s", std::get<intfPos>(group.second).c_str()),
213                 entry("PROPERTY=%s", std::get<propPos>(group.second).c_str()));
214         }
215     }
216     // Setup signal matches for property change events
217     for (auto& prop : std::get<propChangeListPos>(event))
218     {
219         std::unique_ptr<EventData> eventData =
220             std::make_unique<EventData>(
221                 EventData
222                 {
223                     std::get<groupPos>(event),
224                     std::get<handlerObjPos>(prop),
225                     std::get<actionsPos>(event)
226                 }
227             );
228         std::unique_ptr<sdbusplus::server::match::match> match =
229             std::make_unique<sdbusplus::server::match::match>(
230                 _bus,
231                 std::get<signaturePos>(prop).c_str(),
232                 std::bind(std::mem_fn(&Zone::handleEvent),
233                           this,
234                           std::placeholders::_1,
235                           eventData.get())
236             );
237         _signalEvents.emplace_back(std::move(eventData), std::move(match));
238     }
239     // Attach a timer to run the action of an event
240     auto eventTimer = std::get<timerPos>(event);
241     if (std::get<intervalPos>(eventTimer) != seconds(0))
242     {
243         std::unique_ptr<util::Timer> timer =
244             std::make_unique<util::Timer>(
245                 _sdEvents,
246                 [this,
247                  action = &(std::get<actionsPos>(event)),
248                  group = &(std::get<groupPos>(event))]()
249                  {
250                      this->timerExpired(*group, *action);
251                  });
252         if (!timer->running())
253         {
254             timer->start(std::get<intervalPos>(eventTimer),
255                          util::Timer::TimerType::repeating);
256         }
257         _timerEvents.emplace_back(std::move(timer));
258     }
259     // Run action functions for initial event state
260     std::for_each(
261         std::get<actionsPos>(event).begin(),
262         std::get<actionsPos>(event).end(),
263         [this, &event](auto const& action)
264         {
265             action(*this,
266                    std::get<groupPos>(event));
267         });
268 }
269 
270 void Zone::removeEvent(const SetSpeedEvent& event)
271 {
272     // Find the signal event to be removed
273     auto it = std::find_if(
274         _signalEvents.begin(),
275         _signalEvents.end(),
276         [&event](auto const& se)
277         {
278             auto seEventData = *std::get<signalEventDataPos>(se);
279             if (std::get<eventActionsPos>(seEventData).size() !=
280                 std::get<actionsPos>(event).size())
281             {
282                 return false;
283             }
284             else
285             {
286                 // TODO openbmc/openbmc#2328 - Use the action function target
287                 // for comparison
288                 auto actsEqual = [](auto const& a1,
289                                     auto const& a2)
290                         {
291                             return a1.target_type().name() ==
292                                    a2.target_type().name();
293                         };
294                 return
295                 (
296                     std::get<eventGroupPos>(seEventData) ==
297                         std::get<groupPos>(event) &&
298                     std::equal(std::get<actionsPos>(event).begin(),
299                                std::get<actionsPos>(event).end(),
300                                std::get<eventActionsPos>(seEventData).begin(),
301                                actsEqual)
302                 );
303             }
304         });
305     if (it != std::end(_signalEvents))
306     {
307         std::get<signalEventDataPos>(*it).reset();
308         std::get<signalMatchPos>(*it).reset();
309         _signalEvents.erase(it);
310     }
311 }
312 
313 void Zone::refreshProperty(sdbusplus::bus::bus& bus,
314                            const std::string& path,
315                            const std::string& iface,
316                            const std::string& prop)
317 {
318     PropertyVariantType property;
319     getProperty(_bus, path, iface, prop, property);
320     setPropertyValue(path.c_str(), iface.c_str(), prop.c_str(), property);
321 }
322 
323 void Zone::getProperty(sdbusplus::bus::bus& bus,
324                        const std::string& path,
325                        const std::string& iface,
326                        const std::string& prop,
327                        PropertyVariantType& value)
328 {
329     auto serv = util::SDBusPlus::getService(bus, path, iface);
330     auto hostCall = bus.new_method_call(serv.c_str(),
331                                         path.c_str(),
332                                         "org.freedesktop.DBus.Properties",
333                                         "Get");
334     hostCall.append(iface);
335     hostCall.append(prop);
336     auto hostResponseMsg = bus.call(hostCall);
337     if (hostResponseMsg.is_method_error())
338     {
339         log<level::INFO>("Host call response error for retrieving property");
340         elog<InternalFailure>();
341     }
342     hostResponseMsg.read(value);
343 }
344 
345 void Zone::timerExpired(Group eventGroup, std::vector<Action> eventActions)
346 {
347     // Perform the actions
348     std::for_each(eventActions.begin(),
349                   eventActions.end(),
350                   [this, &eventGroup](auto const& action)
351                   {
352                       action(*this, eventGroup);
353                   });
354 }
355 
356 void Zone::handleEvent(sdbusplus::message::message& msg,
357                        const EventData* eventData)
358 {
359     // Handle the callback
360     std::get<eventHandlerPos>(*eventData)(_bus, msg, *this);
361     // Perform the actions
362     std::for_each(
363         std::get<eventActionsPos>(*eventData).begin(),
364         std::get<eventActionsPos>(*eventData).end(),
365         [this, &eventData](auto const& action)
366         {
367             action(*this,
368                    std::get<eventGroupPos>(*eventData));
369         });
370 }
371 
372 }
373 }
374 }
375