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 <functional>
18 #include <fstream>
19 #include <cereal/cereal.hpp>
20 #include <cereal/archives/json.hpp>
21 #include <experimental/filesystem>
22 #include <phosphor-logging/log.hpp>
23 #include <phosphor-logging/elog.hpp>
24 #include <phosphor-logging/elog-errors.hpp>
25 #include <stdexcept>
26 #include <xyz/openbmc_project/Common/error.hpp>
27 #include "config.h"
28 #include "zone.hpp"
29 #include "utility.hpp"
30 #include "sdbusplus.hpp"
31 
32 namespace phosphor
33 {
34 namespace fan
35 {
36 namespace control
37 {
38 
39 using namespace std::chrono;
40 using namespace phosphor::fan;
41 using namespace phosphor::logging;
42 namespace fs = std::experimental::filesystem;
43 using InternalFailure = sdbusplus::xyz::openbmc_project::Common::
44                              Error::InternalFailure;
45 
46 Zone::Zone(Mode mode,
47            sdbusplus::bus::bus& bus,
48            const std::string& path,
49            const sdeventplus::Event& event,
50            const ZoneDefinition& def) :
51     ThermalObject(bus, path.c_str(), true),
52     _bus(bus),
53     _path(path),
54     _ifaces({"xyz.openbmc_project.Control.ThermalMode"}),
55     _fullSpeed(std::get<fullSpeedPos>(def)),
56     _zoneNum(std::get<zoneNumPos>(def)),
57     _defFloorSpeed(std::get<floorSpeedPos>(def)),
58     _defCeilingSpeed(std::get<fullSpeedPos>(def)),
59     _incDelay(std::get<incDelayPos>(def)),
60     _decInterval(std::get<decIntervalPos>(def)),
61     _incTimer(event, std::bind(&Zone::incTimerExpired, this)),
62     _decTimer(event, std::bind(&Zone::decTimerExpired, this)),
63     _eventLoop(event)
64 {
65     auto& fanDefs = std::get<fanListPos>(def);
66 
67     for (auto& def : fanDefs)
68     {
69         _fans.emplace_back(std::make_unique<Fan>(bus, def));
70     }
71 
72     // Do not enable set speed events when in init mode
73     if (mode == Mode::control)
74     {
75         // Process any zone handlers defined
76         for (auto& hand : std::get<handlerPos>(def))
77         {
78             hand(*this);
79         }
80 
81         // Restore thermal control current mode state
82         restoreCurrentMode();
83 
84         // Emit objects added in control mode only
85         this->emit_object_added();
86 
87         // Update target speed to current zone target speed
88         if (!_fans.empty())
89         {
90             _targetSpeed = _fans.front()->getTargetSpeed();
91         }
92         // Setup signal trigger for set speed events
93         for (auto& event : std::get<setSpeedEventsPos>(def))
94         {
95             initEvent(event);
96         }
97         // Start timer for fan speed decreases
98         _decTimer.restart(_decInterval);
99     }
100 }
101 
102 void Zone::setSpeed(uint64_t speed)
103 {
104     if (_isActive)
105     {
106         _targetSpeed = speed;
107         for (auto& fan : _fans)
108         {
109             fan->setSpeed(_targetSpeed);
110         }
111     }
112 }
113 
114 void Zone::setFullSpeed()
115 {
116     if (_fullSpeed != 0)
117     {
118         _targetSpeed = _fullSpeed;
119         for (auto& fan : _fans)
120         {
121             fan->setSpeed(_targetSpeed);
122         }
123     }
124 }
125 
126 void Zone::setActiveAllow(const Group* group, bool isActiveAllow)
127 {
128     _active[*(group)] = isActiveAllow;
129     if (!isActiveAllow)
130     {
131         _isActive = false;
132     }
133     else
134     {
135         // Check all entries are set to allow control active
136         auto actPred = [](auto const& entry) {return entry.second;};
137         _isActive = std::all_of(_active.begin(),
138                                 _active.end(),
139                                 actPred);
140     }
141 }
142 
143 void Zone::removeService(const Group* group,
144                          const std::string& name)
145 {
146     try
147     {
148         auto& sNames = _services.at(*group);
149         auto it = std::find_if(
150             sNames.begin(),
151             sNames.end(),
152             [&name](auto const& entry)
153             {
154                 return name == std::get<namePos>(entry);
155             }
156         );
157         if (it != std::end(sNames))
158         {
159             // Remove service name from group
160             sNames.erase(it);
161         }
162     }
163     catch (const std::out_of_range& oore)
164     {
165         // No services for group found
166     }
167 }
168 
169 void Zone::setServiceOwner(const Group* group,
170                            const std::string& name,
171                            const bool hasOwner)
172 {
173     try
174     {
175         auto& sNames = _services.at(*group);
176         auto it = std::find_if(
177             sNames.begin(),
178             sNames.end(),
179             [&name](auto const& entry)
180             {
181                 return name == std::get<namePos>(entry);
182             }
183         );
184         if (it != std::end(sNames))
185         {
186             std::get<hasOwnerPos>(*it) = hasOwner;
187         }
188         else
189         {
190             _services[*group].emplace_back(name, hasOwner);
191         }
192     }
193     catch (const std::out_of_range& oore)
194     {
195         _services[*group].emplace_back(name, hasOwner);
196     }
197 }
198 
199 void Zone::setServices(const Group* group)
200 {
201     // Remove the empty service name if exists
202     removeService(group, "");
203     for (auto it = group->begin(); it != group->end(); ++it)
204     {
205         std::string name;
206         bool hasOwner = false;
207         try
208         {
209             name = getService(it->first,
210                               std::get<intfPos>(it->second));
211             hasOwner = util::SDBusPlus::callMethodAndRead<bool>(
212                     _bus,
213                     "org.freedesktop.DBus",
214                     "/org/freedesktop/DBus",
215                     "org.freedesktop.DBus",
216                     "NameHasOwner",
217                     name);
218         }
219         catch (const util::DBusMethodError& e)
220         {
221             // Failed to get service name owner state
222             hasOwner = false;
223         }
224         setServiceOwner(group, name, hasOwner);
225     }
226 }
227 
228 void Zone::setFloor(uint64_t speed)
229 {
230     // Check all entries are set to allow floor to be set
231     auto pred = [](auto const& entry) {return entry.second;};
232     auto setFloor = std::all_of(_floorChange.begin(),
233                                 _floorChange.end(),
234                                 pred);
235     if (setFloor)
236     {
237         _floorSpeed = speed;
238         // Floor speed above target, update target to floor speed
239         if (_targetSpeed < _floorSpeed)
240         {
241             requestSpeedIncrease(_floorSpeed - _targetSpeed);
242         }
243     }
244 }
245 
246 void Zone::requestSpeedIncrease(uint64_t targetDelta)
247 {
248     // Only increase speed when delta is higher than
249     // the current increase delta for the zone and currently under ceiling
250     if (targetDelta > _incSpeedDelta &&
251         _targetSpeed < _ceilingSpeed)
252     {
253         auto requestTarget = getRequestSpeedBase();
254         requestTarget = (targetDelta - _incSpeedDelta) + requestTarget;
255         _incSpeedDelta = targetDelta;
256         // Target speed can not go above a defined ceiling speed
257         if (requestTarget > _ceilingSpeed)
258         {
259             requestTarget = _ceilingSpeed;
260         }
261         setSpeed(requestTarget);
262         // Retart timer countdown for fan speed increase
263         _incTimer.restartOnce(_incDelay);
264     }
265 }
266 
267 void Zone::incTimerExpired()
268 {
269     // Clear increase delta when timer expires allowing additional speed
270     // increase requests or speed decreases to occur
271     _incSpeedDelta = 0;
272 }
273 
274 void Zone::requestSpeedDecrease(uint64_t targetDelta)
275 {
276     // Only decrease the lowest target delta requested
277     if (_decSpeedDelta == 0 || targetDelta < _decSpeedDelta)
278     {
279         _decSpeedDelta = targetDelta;
280     }
281 }
282 
283 void Zone::decTimerExpired()
284 {
285     // Check all entries are set to allow a decrease
286     auto pred = [](auto const& entry) {return entry.second;};
287     auto decAllowed = std::all_of(_decAllowed.begin(),
288                                   _decAllowed.end(),
289                                   pred);
290 
291     // Only decrease speeds when allowed,
292     // where no requested increases exist and
293     // the increase timer is not running
294     // (i.e. not in the middle of increasing)
295     if (decAllowed && _incSpeedDelta == 0 && !_incTimer.isEnabled())
296     {
297         auto requestTarget = getRequestSpeedBase();
298         // Request target speed should not start above ceiling
299         if (requestTarget > _ceilingSpeed)
300         {
301             requestTarget = _ceilingSpeed;
302         }
303         // Target speed can not go below the defined floor speed
304         if ((requestTarget < _decSpeedDelta) ||
305             (requestTarget - _decSpeedDelta < _floorSpeed))
306         {
307             requestTarget = _floorSpeed;
308         }
309         else
310         {
311             requestTarget = requestTarget - _decSpeedDelta;
312         }
313         setSpeed(requestTarget);
314     }
315     // Clear decrease delta when timer expires
316     _decSpeedDelta = 0;
317     // Decrease timer is restarted since its repeating
318 }
319 
320 void Zone::initEvent(const SetSpeedEvent& event)
321 {
322     sdbusplus::message::message nullMsg{nullptr};
323 
324     for (auto& sig : std::get<signalsPos>(event))
325     {
326         // Setup signal matches of the property for event
327         std::unique_ptr<EventData> eventData =
328             std::make_unique<EventData>(
329                     std::get<groupPos>(event),
330                     std::get<sigMatchPos>(sig),
331                     std::get<sigHandlerPos>(sig),
332                     std::get<actionsPos>(event)
333             );
334 
335         // When match is empty, handle if zone object member
336         if (std::get<sigMatchPos>(sig).empty())
337         {
338             // Set event data for each host group member
339             for (auto it = std::get<groupPos>(event).begin();
340                  it != std::get<groupPos>(event).end(); ++it)
341             {
342                 if (it->first == _path)
343                 {
344                     // Group member interface in list owned by zone
345                     if (std::find(_ifaces.begin(), _ifaces.end(),
346                         std::get<intfPos>(it->second)) != _ifaces.end())
347                     {
348                         // Store path,interface,property as a managed object
349                         _objects[it->first]
350                                 [std::get<intfPos>(it->second)]
351                                 [std::get<propPos>(it->second)] =
352                                         eventData.get();
353                     }
354                 }
355             }
356         }
357 
358         // Initialize the event signal using handler
359         std::get<sigHandlerPos>(sig)(_bus, nullMsg, *this);
360 
361         // Subscribe to signal match
362         std::unique_ptr<sdbusplus::server::match::match> match = nullptr;
363         if (!std::get<sigMatchPos>(sig).empty())
364         {
365             match = std::make_unique<sdbusplus::server::match::match>(
366                     _bus,
367                     std::get<sigMatchPos>(sig).c_str(),
368                     std::bind(std::mem_fn(&Zone::handleEvent),
369                               this,
370                               std::placeholders::_1,
371                               eventData.get())
372                 );
373         }
374 
375         _signalEvents.emplace_back(std::move(eventData), std::move(match));
376     }
377     // Attach a timer to run the action of an event
378     auto timerConf = std::get<timerConfPos>(event);
379     if (std::get<intervalPos>(timerConf) != seconds(0))
380     {
381         addTimer(std::get<groupPos>(event),
382                  std::get<actionsPos>(event),
383                  timerConf);
384     }
385     // Run action functions for initial event state
386     std::for_each(
387         std::get<actionsPos>(event).begin(),
388         std::get<actionsPos>(event).end(),
389         [this, &event](auto const& action)
390         {
391             action(*this,
392                    std::get<groupPos>(event));
393         });
394 }
395 
396 void Zone::removeEvent(const SetSpeedEvent& event)
397 {
398     // Remove signals of the event
399     for (auto& sig : std::get<signalsPos>(event))
400     {
401         auto it = findSignal(sig,
402                              std::get<groupPos>(event),
403                              std::get<actionsPos>(event));
404         if (it != std::end(getSignalEvents()))
405         {
406             removeSignal(it);
407         }
408     }
409     // Remove timers of the event
410     if (std::get<intervalPos>(std::get<timerConfPos>(event)) != seconds(0))
411     {
412         auto it = findTimer(std::get<groupPos>(event),
413                             std::get<actionsPos>(event));
414         if (it != std::end(getTimerEvents()))
415         {
416             removeTimer(it);
417         }
418     }
419 }
420 
421 std::vector<SignalEvent>::iterator Zone::findSignal(
422         const Signal& signal,
423         const Group& eGroup,
424         const std::vector<Action>& eActions)
425 {
426     // Find the signal in the event to be removed
427     for (auto it = _signalEvents.begin(); it != _signalEvents.end(); ++ it)
428     {
429         const auto& seEventData = *std::get<signalEventDataPos>(*it);
430         if (eGroup == std::get<eventGroupPos>(seEventData) &&
431             std::get<sigMatchPos>(signal) ==
432                 std::get<eventMatchPos>(seEventData) &&
433             std::get<sigHandlerPos>(signal).target_type().name() ==
434                 std::get<eventHandlerPos>(seEventData).target_type().name() &&
435             eActions.size() == std::get<eventActionsPos>(seEventData).size())
436         {
437             // TODO openbmc/openbmc#2328 - Use the function target
438             // for comparison
439             auto actsEqual = [](auto const& a1,
440                                 auto const& a2)
441                     {
442                         return a1.target_type().name() ==
443                                a2.target_type().name();
444                     };
445             if (std::equal(eActions.begin(),
446                            eActions.end(),
447                            std::get<eventActionsPos>(seEventData).begin(),
448                            actsEqual))
449             {
450                 return it;
451             }
452         }
453     }
454 
455     return _signalEvents.end();
456 }
457 
458 std::vector<TimerEvent>::iterator Zone::findTimer(
459         const Group& eventGroup,
460         const std::vector<Action>& eventActions)
461 {
462     for (auto it = _timerEvents.begin(); it != _timerEvents.end(); ++it)
463     {
464         const auto& teEventData = *std::get<timerEventDataPos>(*it);
465         if (std::get<eventActionsPos>(teEventData).size() ==
466             eventActions.size())
467         {
468             // TODO openbmc/openbmc#2328 - Use the action function target
469             // for comparison
470             auto actsEqual = [](auto const& a1,
471                                 auto const& a2)
472                     {
473                         return a1.target_type().name() ==
474                                a2.target_type().name();
475                     };
476             if (std::get<eventGroupPos>(teEventData) == eventGroup &&
477                 std::equal(eventActions.begin(),
478                            eventActions.end(),
479                            std::get<eventActionsPos>(teEventData).begin(),
480                            actsEqual))
481             {
482                 return it;
483             }
484         }
485     }
486 
487     return _timerEvents.end();
488 }
489 
490 void Zone::addTimer(const Group& group,
491                     const std::vector<Action>& actions,
492                     const TimerConf& tConf)
493 {
494     auto eventData = std::make_unique<EventData>(
495             group,
496             "",
497             nullptr,
498             actions
499     );
500     Timer timer(
501         _eventLoop,
502         std::bind(&Zone::timerExpired,
503                   this,
504                   std::cref(std::get<Group>(*eventData)),
505                   std::cref(std::get<std::vector<Action>>(*eventData))));
506     if (std::get<TimerType>(tConf) == TimerType::repeating)
507     {
508         timer.restart(std::get<intervalPos>(tConf));
509     }
510     else if (std::get<TimerType>(tConf) == TimerType::oneshot)
511     {
512         timer.restartOnce(std::get<intervalPos>(tConf));
513     }
514     else
515     {
516         throw std::invalid_argument("Invalid Timer Type");
517     }
518     _timerEvents.emplace_back(std::move(eventData), std::move(timer));
519 }
520 
521 void Zone::timerExpired(const Group& eventGroup,
522                         const std::vector<Action>& eventActions)
523 {
524     // Perform the actions
525     std::for_each(eventActions.begin(),
526                   eventActions.end(),
527                   [this, &eventGroup](auto const& action)
528                   {
529                       action(*this, eventGroup);
530                   });
531 }
532 
533 void Zone::handleEvent(sdbusplus::message::message& msg,
534                        const EventData* eventData)
535 {
536     // Handle the callback
537     std::get<eventHandlerPos>(*eventData)(_bus, msg, *this);
538     // Perform the actions
539     std::for_each(
540         std::get<eventActionsPos>(*eventData).begin(),
541         std::get<eventActionsPos>(*eventData).end(),
542         [this, &eventData](auto const& action)
543         {
544             action(*this,
545                    std::get<eventGroupPos>(*eventData));
546         });
547 }
548 
549 const std::string& Zone::getService(const std::string& path,
550                                     const std::string& intf)
551 {
552     // Retrieve service from cache
553     auto srvIter = _servTree.find(path);
554     if (srvIter != _servTree.end())
555     {
556         for (auto& serv : srvIter->second)
557         {
558             auto it = std::find_if(
559                 serv.second.begin(),
560                 serv.second.end(),
561                 [&intf](auto const& interface)
562                 {
563                     return intf == interface;
564                 });
565             if (it != std::end(serv.second))
566             {
567                 // Service found
568                 return serv.first;
569             }
570         }
571         // Interface not found in cache, add and return
572         return addServices(path, intf, 0);
573     }
574     else
575     {
576         // Path not found in cache, add and return
577         return addServices(path, intf, 0);
578     }
579 }
580 
581 const std::string& Zone::addServices(const std::string& path,
582                                      const std::string& intf,
583                                      int32_t depth)
584 {
585     static const std::string empty = "";
586     auto it = _servTree.end();
587 
588     // Get all subtree objects for the given interface
589     auto objects = util::SDBusPlus::getSubTree(_bus, "/", intf, depth);
590     // Add what's returned to the cache of path->services
591     for (auto& pIter : objects)
592     {
593         auto pathIter = _servTree.find(pIter.first);
594         if (pathIter != _servTree.end())
595         {
596             // Path found in cache
597             for (auto& sIter : pIter.second)
598             {
599                 auto servIter = pathIter->second.find(sIter.first);
600                 if (servIter != pathIter->second.end())
601                 {
602                     // Service found in cache
603                     for (auto& iIter : sIter.second)
604                     {
605                         if (std::find(servIter->second.begin(),
606                                       servIter->second.end(),
607                                       iIter) == servIter->second.end())
608                         {
609                             // Add interface to cache
610                             servIter->second.emplace_back(iIter);
611                         }
612                     }
613                 }
614                 else
615                 {
616                     // Service not found in cache
617                     pathIter->second.insert(sIter);
618                 }
619             }
620         }
621         else
622         {
623             _servTree.insert(pIter);
624         }
625         // When the paths match, since a single interface constraint is given,
626         // that is the service to return
627         if (path == pIter.first)
628         {
629             it = _servTree.find(pIter.first);
630         }
631     }
632 
633     if (it != _servTree.end())
634     {
635         return it->second.begin()->first;
636     }
637 
638     return empty;
639 }
640 
641 auto Zone::getPersisted(const std::string& intf,
642                         const std::string& prop)
643 {
644     auto persisted = false;
645 
646     auto it = _persisted.find(intf);
647     if (it != _persisted.end())
648     {
649         return std::any_of(it->second.begin(),
650                            it->second.end(),
651                            [&prop](auto& p)
652                            {
653                                return prop == p;
654                            });
655     }
656 
657     return persisted;
658 }
659 
660 std::string Zone::current(std::string value)
661 {
662     auto current = ThermalObject::current();
663     std::transform(value.begin(), value.end(), value.begin(), toupper);
664 
665     auto supported = ThermalObject::supported();
666     auto isSupported = std::any_of(
667         supported.begin(),
668         supported.end(),
669         [&value](auto& s)
670         {
671             std::transform(s.begin(), s.end(), s.begin(), toupper);
672             return value == s;
673         });
674 
675     if (value != current && isSupported)
676     {
677         current = ThermalObject::current(value);
678         if (getPersisted("xyz.openbmc_project.Control.ThermalMode", "Current"))
679         {
680             saveCurrentMode();
681         }
682         // Trigger event(s) for current mode property change
683         auto eData = _objects[_path]
684                              ["xyz.openbmc_project.Control.ThermalMode"]
685                              ["Current"];
686         if (eData != nullptr)
687         {
688             sdbusplus::message::message nullMsg{nullptr};
689             handleEvent(nullMsg, eData);
690         }
691     }
692 
693     return current;
694 }
695 
696 void Zone::saveCurrentMode()
697 {
698     fs::path path{CONTROL_PERSIST_ROOT_PATH};
699     // Append zone and property description
700     path /= std::to_string(_zoneNum);
701     path /= "CurrentMode";
702     std::ofstream ofs(path.c_str(), std::ios::binary);
703     cereal::JSONOutputArchive oArch(ofs);
704     oArch(ThermalObject::current());
705 }
706 
707 void Zone::restoreCurrentMode()
708 {
709     auto current = ThermalObject::current();
710     fs::path path{CONTROL_PERSIST_ROOT_PATH};
711     path /= std::to_string(_zoneNum);
712     path /= "CurrentMode";
713     fs::create_directories(path.parent_path());
714 
715     try
716     {
717         if (fs::exists(path))
718         {
719             std::ifstream ifs(path.c_str(), std::ios::in | std::ios::binary);
720             cereal::JSONInputArchive iArch(ifs);
721             iArch(current);
722         }
723     }
724     catch (std::exception& e)
725     {
726         log<level::ERR>(e.what());
727         fs::remove(path);
728         current = ThermalObject::current();
729     }
730 
731     this->current(current);
732 }
733 
734 }
735 }
736 }
737