xref: /openbmc/phosphor-fan-presence/control/zone.cpp (revision 8e5d197b840d4498dcb714b60cc1d38202a7a7a7)
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         // Update target speed to current zone target speed
64         if (!_fans.empty())
65         {
66             _targetSpeed = _fans.front()->getTargetSpeed();
67         }
68         // Setup signal trigger for set speed events
69         for (auto& event : std::get<setSpeedEventsPos>(def))
70         {
71             initEvent(event);
72         }
73         // Start timer for fan speed decreases
74         if (!_decTimer.running() && _decInterval != seconds::zero())
75         {
76             _decTimer.start(_decInterval,
77                             util::Timer::TimerType::repeating);
78         }
79     }
80 }
81 
82 void Zone::setSpeed(uint64_t speed)
83 {
84     if (_isActive)
85     {
86         _targetSpeed = speed;
87         for (auto& fan : _fans)
88         {
89             fan->setSpeed(_targetSpeed);
90         }
91     }
92 }
93 
94 void Zone::setFullSpeed()
95 {
96     if (_fullSpeed != 0)
97     {
98         _targetSpeed = _fullSpeed;
99         for (auto& fan : _fans)
100         {
101             fan->setSpeed(_targetSpeed);
102         }
103     }
104 }
105 
106 void Zone::setActiveAllow(const Group* group, bool isActiveAllow)
107 {
108     _active[*(group)] = isActiveAllow;
109     if (!isActiveAllow)
110     {
111         _isActive = false;
112     }
113     else
114     {
115         // Check all entries are set to allow control active
116         auto actPred = [](auto const& entry) {return entry.second;};
117         _isActive = std::all_of(_active.begin(),
118                                 _active.end(),
119                                 actPred);
120     }
121 }
122 
123 void Zone::removeService(const Group* group,
124                          const std::string& name)
125 {
126     try
127     {
128         auto& sNames = _services.at(*group);
129         auto it = std::find_if(
130             sNames.begin(),
131             sNames.end(),
132             [&name](auto const& entry)
133             {
134                 return name == std::get<namePos>(entry);
135             }
136         );
137         if (it != std::end(sNames))
138         {
139             // Remove service name from group
140             sNames.erase(it);
141         }
142     }
143     catch (const std::out_of_range& oore)
144     {
145         // No services for group found
146     }
147 }
148 
149 void Zone::setServiceOwner(const Group* group,
150                            const std::string& name,
151                            const bool hasOwner)
152 {
153     try
154     {
155         auto& sNames = _services.at(*group);
156         auto it = std::find_if(
157             sNames.begin(),
158             sNames.end(),
159             [&name](auto const& entry)
160             {
161                 return name == std::get<namePos>(entry);
162             }
163         );
164         if (it != std::end(sNames))
165         {
166             std::get<hasOwnerPos>(*it) = hasOwner;
167         }
168         else
169         {
170             _services[*group].emplace_back(name, hasOwner);
171         }
172     }
173     catch (const std::out_of_range& oore)
174     {
175         _services[*group].emplace_back(name, hasOwner);
176     }
177 }
178 
179 void Zone::setServices(const Group* group)
180 {
181     // Remove the empty service name if exists
182     removeService(group, "");
183     for (auto it = group->begin(); it != group->end(); ++it)
184     {
185         std::string name;
186         bool hasOwner = false;
187         try
188         {
189             name = util::SDBusPlus::getService(
190                     _bus,
191                     it->first,
192                     std::get<intfPos>(it->second));
193             hasOwner = util::SDBusPlus::callMethodAndRead<bool>(
194                     _bus,
195                     "org.freedesktop.DBus",
196                     "/org/freedesktop/DBus",
197                     "org.freedesktop.DBus",
198                     "NameHasOwner",
199                     name);
200         }
201         catch (const InternalFailure& ife)
202         {
203             // Failed to get service name owner state
204             hasOwner = false;
205         }
206         setServiceOwner(group, name, hasOwner);
207     }
208 }
209 
210 void Zone::setFloor(uint64_t speed)
211 {
212     // Check all entries are set to allow floor to be set
213     auto pred = [](auto const& entry) {return entry.second;};
214     auto setFloor = std::all_of(_floorChange.begin(),
215                                 _floorChange.end(),
216                                 pred);
217     if (setFloor)
218     {
219         _floorSpeed = speed;
220         // Floor speed above target, update target to floor speed
221         if (_targetSpeed < _floorSpeed)
222         {
223             requestSpeedIncrease(_floorSpeed - _targetSpeed);
224         }
225     }
226 }
227 
228 void Zone::requestSpeedIncrease(uint64_t targetDelta)
229 {
230     // Only increase speed when delta is higher than
231     // the current increase delta for the zone and currently under ceiling
232     if (targetDelta > _incSpeedDelta &&
233         _targetSpeed < _ceilingSpeed)
234     {
235         auto requestTarget = getRequestSpeedBase();
236         requestTarget = (targetDelta - _incSpeedDelta) + requestTarget;
237         _incSpeedDelta = targetDelta;
238         // Target speed can not go above a defined ceiling speed
239         if (requestTarget > _ceilingSpeed)
240         {
241             requestTarget = _ceilingSpeed;
242         }
243         // Cancel current timer countdown
244         if (_incTimer.running())
245         {
246             _incTimer.stop();
247         }
248         setSpeed(requestTarget);
249         // Start timer countdown for fan speed increase
250         _incTimer.start(_incDelay,
251                         util::Timer::TimerType::oneshot);
252     }
253 }
254 
255 void Zone::incTimerExpired()
256 {
257     // Clear increase delta when timer expires allowing additional speed
258     // increase requests or speed decreases to occur
259     _incSpeedDelta = 0;
260 }
261 
262 void Zone::requestSpeedDecrease(uint64_t targetDelta)
263 {
264     // Only decrease the lowest target delta requested
265     if (_decSpeedDelta == 0 || targetDelta < _decSpeedDelta)
266     {
267         _decSpeedDelta = targetDelta;
268     }
269 }
270 
271 void Zone::decTimerExpired()
272 {
273     // Check all entries are set to allow a decrease
274     auto pred = [](auto const& entry) {return entry.second;};
275     auto decAllowed = std::all_of(_decAllowed.begin(),
276                                   _decAllowed.end(),
277                                   pred);
278 
279     // Only decrease speeds when allowed,
280     // where no requested increases exist and
281     // the increase timer is not running
282     // (i.e. not in the middle of increasing)
283     if (decAllowed && _incSpeedDelta == 0 && !_incTimer.running())
284     {
285         auto requestTarget = getRequestSpeedBase();
286         // Request target speed should not start above ceiling
287         if (requestTarget > _ceilingSpeed)
288         {
289             requestTarget = _ceilingSpeed;
290         }
291         // Target speed can not go below the defined floor speed
292         if ((requestTarget < _decSpeedDelta) ||
293             (requestTarget - _decSpeedDelta < _floorSpeed))
294         {
295             requestTarget = _floorSpeed;
296         }
297         else
298         {
299             requestTarget = requestTarget - _decSpeedDelta;
300         }
301         setSpeed(requestTarget);
302     }
303     // Clear decrease delta when timer expires
304     _decSpeedDelta = 0;
305     // Decrease timer is restarted since its repeating
306 }
307 
308 void Zone::initEvent(const SetSpeedEvent& event)
309 {
310     sdbusplus::message::message nullMsg{nullptr};
311 
312     for (auto& sig : std::get<signalsPos>(event))
313     {
314         // Initialize the event signal using handler
315         std::get<sigHandlerPos>(sig)(_bus, nullMsg, *this);
316         // Setup signal matches of the property for event
317         std::unique_ptr<EventData> eventData =
318             std::make_unique<EventData>(
319                     std::get<groupPos>(event),
320                     std::get<sigMatchPos>(sig),
321                     std::get<sigHandlerPos>(sig),
322                     std::get<actionsPos>(event)
323             );
324         std::unique_ptr<sdbusplus::server::match::match> match = nullptr;
325         if (!std::get<sigMatchPos>(sig).empty())
326         {
327             match = std::make_unique<sdbusplus::server::match::match>(
328                     _bus,
329                     std::get<sigMatchPos>(sig).c_str(),
330                     std::bind(std::mem_fn(&Zone::handleEvent),
331                               this,
332                               std::placeholders::_1,
333                               eventData.get())
334                 );
335         }
336         _signalEvents.emplace_back(std::move(eventData), std::move(match));
337     }
338     // Attach a timer to run the action of an event
339     auto eventTimer = std::get<timerPos>(event);
340     if (std::get<intervalPos>(eventTimer) != seconds(0))
341     {
342         // Associate event data with timer
343         std::unique_ptr<EventData> eventData =
344             std::make_unique<EventData>(
345                     std::get<groupPos>(event),
346                     "",
347                     nullptr,
348                     std::get<actionsPos>(event)
349             );
350         std::unique_ptr<util::Timer> timer =
351             std::make_unique<util::Timer>(
352                 _sdEvents,
353                 [this,
354                  action = &(std::get<actionsPos>(event)),
355                  group = &(std::get<groupPos>(event))]()
356                  {
357                      this->timerExpired(*group, *action);
358                  });
359         if (!timer->running())
360         {
361             timer->start(std::get<intervalPos>(eventTimer),
362                          std::get<typePos>(eventTimer));
363         }
364         addTimer(std::move(eventData), std::move(timer));
365     }
366     // Run action functions for initial event state
367     std::for_each(
368         std::get<actionsPos>(event).begin(),
369         std::get<actionsPos>(event).end(),
370         [this, &event](auto const& action)
371         {
372             action(*this,
373                    std::get<groupPos>(event));
374         });
375 }
376 
377 void Zone::removeEvent(const SetSpeedEvent& event)
378 {
379     // Find the signal event to be removed
380     auto it = std::find_if(
381         _signalEvents.begin(),
382         _signalEvents.end(),
383         [&event](auto const& se)
384         {
385             auto seEventData = *std::get<signalEventDataPos>(se);
386             if (std::get<eventActionsPos>(seEventData).size() !=
387                 std::get<actionsPos>(event).size())
388             {
389                 return false;
390             }
391             else
392             {
393                 // TODO openbmc/openbmc#2328 - Use the action function target
394                 // for comparison
395                 auto actsEqual = [](auto const& a1,
396                                     auto const& a2)
397                         {
398                             return a1.target_type().name() ==
399                                    a2.target_type().name();
400                         };
401                 return
402                 (
403                     std::get<eventGroupPos>(seEventData) ==
404                         std::get<groupPos>(event) &&
405                     std::equal(std::get<actionsPos>(event).begin(),
406                                std::get<actionsPos>(event).end(),
407                                std::get<eventActionsPos>(seEventData).begin(),
408                                actsEqual)
409                 );
410             }
411         });
412     if (it != std::end(_signalEvents))
413     {
414         std::get<signalEventDataPos>(*it).reset();
415         if (std::get<signalMatchPos>(*it) != nullptr)
416         {
417             std::get<signalMatchPos>(*it).reset();
418         }
419         _signalEvents.erase(it);
420     }
421 }
422 
423 std::vector<TimerEvent>::iterator Zone::findTimer(
424         const Group& eventGroup,
425         const std::vector<Action>& eventActions)
426 {
427     for (auto it = _timerEvents.begin(); it != _timerEvents.end(); ++it)
428     {
429         auto teEventData = *std::get<timerEventDataPos>(*it);
430         if (std::get<eventActionsPos>(teEventData).size() ==
431             eventActions.size())
432         {
433             // TODO openbmc/openbmc#2328 - Use the action function target
434             // for comparison
435             auto actsEqual = [](auto const& a1,
436                                 auto const& a2)
437                     {
438                         return a1.target_type().name() ==
439                                a2.target_type().name();
440                     };
441             if (std::get<eventGroupPos>(teEventData) == eventGroup &&
442                 std::equal(eventActions.begin(),
443                            eventActions.end(),
444                            std::get<eventActionsPos>(teEventData).begin(),
445                            actsEqual))
446             {
447                 return it;
448             }
449         }
450     }
451 
452     return _timerEvents.end();
453 }
454 
455 void Zone::timerExpired(Group eventGroup, std::vector<Action> eventActions)
456 {
457     // Perform the actions
458     std::for_each(eventActions.begin(),
459                   eventActions.end(),
460                   [this, &eventGroup](auto const& action)
461                   {
462                       action(*this, eventGroup);
463                   });
464 }
465 
466 void Zone::handleEvent(sdbusplus::message::message& msg,
467                        const EventData* eventData)
468 {
469     // Handle the callback
470     std::get<eventHandlerPos>(*eventData)(_bus, msg, *this);
471     // Perform the actions
472     std::for_each(
473         std::get<eventActionsPos>(*eventData).begin(),
474         std::get<eventActionsPos>(*eventData).end(),
475         [this, &eventData](auto const& action)
476         {
477             action(*this,
478                    std::get<eventGroupPos>(*eventData));
479         });
480 }
481 
482 }
483 }
484 }
485