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