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