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(std::get<pathPos>(*it),
210                               std::get<intfPos>(*it));
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     // Enable event triggers
323     std::for_each(
324         std::get<triggerPos>(event).begin(),
325         std::get<triggerPos>(event).end(),
326         [this, &event](auto const& trigger)
327         {
328             trigger(*this,
329                     std::get<groupPos>(event),
330                     std::get<actionsPos>(event));
331         }
332     );
333 
334     // Run action functions for initial event state
335     std::for_each(
336         std::get<actionsPos>(event).begin(),
337         std::get<actionsPos>(event).end(),
338         [this, &event](auto const& action)
339         {
340             action(*this,
341                    std::get<groupPos>(event));
342         });
343 }
344 
345 void Zone::removeEvent(const SetSpeedEvent& event)
346 {
347     // Remove triggers of the event
348     for (auto& trig : std::get<triggerPos>(event))
349     {
350         auto it = findSignal(trig,
351                              std::get<groupPos>(event),
352                              std::get<actionsPos>(event));
353         if (it != std::end(getSignalEvents()))
354         {
355             removeSignal(it);
356         }
357     }
358     // Remove timers of the event
359     auto it = findTimer(std::get<groupPos>(event),
360                         std::get<actionsPos>(event));
361     if (it != std::end(getTimerEvents()))
362     {
363         removeTimer(it);
364     }
365 }
366 
367 std::vector<SignalEvent>::iterator Zone::findSignal(
368         const Trigger& signal,
369         const Group& eGroup,
370         const std::vector<Action>& eActions)
371 {
372     // Find the signal in the event to be removed
373     for (auto it = _signalEvents.begin(); it != _signalEvents.end(); ++ it)
374     {
375         const auto& seEventData = *std::get<signalEventDataPos>(*it);
376         if (eGroup == std::get<eventGroupPos>(seEventData) &&
377             eActions.size() == std::get<eventActionsPos>(seEventData).size())
378         {
379             // TODO openbmc/openbmc#2328 - Use the function target
380             // for comparison
381             auto actsEqual = [](auto const& a1,
382                                 auto const& a2)
383                     {
384                         return a1.target_type().name() ==
385                                a2.target_type().name();
386                     };
387             if (std::equal(eActions.begin(),
388                            eActions.end(),
389                            std::get<eventActionsPos>(seEventData).begin(),
390                            actsEqual))
391             {
392                 return it;
393             }
394         }
395     }
396 
397     return _signalEvents.end();
398 }
399 
400 std::vector<TimerEvent>::iterator Zone::findTimer(
401         const Group& eventGroup,
402         const std::vector<Action>& eventActions)
403 {
404     for (auto it = _timerEvents.begin(); it != _timerEvents.end(); ++it)
405     {
406         const auto& teEventData = *std::get<timerEventDataPos>(*it);
407         if (std::get<eventActionsPos>(teEventData).size() ==
408             eventActions.size())
409         {
410             // TODO openbmc/openbmc#2328 - Use the action function target
411             // for comparison
412             auto actsEqual = [](auto const& a1,
413                                 auto const& a2)
414                     {
415                         return a1.target_type().name() ==
416                                a2.target_type().name();
417                     };
418             if (std::get<eventGroupPos>(teEventData) == eventGroup &&
419                 std::equal(eventActions.begin(),
420                            eventActions.end(),
421                            std::get<eventActionsPos>(teEventData).begin(),
422                            actsEqual))
423             {
424                 return it;
425             }
426         }
427     }
428 
429     return _timerEvents.end();
430 }
431 
432 void Zone::addTimer(const Group& group,
433                     const std::vector<Action>& actions,
434                     const TimerConf& tConf)
435 {
436     auto eventData = std::make_unique<EventData>(
437             group,
438             "",
439             nullptr,
440             actions
441     );
442     Timer timer(
443         _eventLoop,
444         std::bind(&Zone::timerExpired,
445                   this,
446                   std::cref(std::get<Group>(*eventData)),
447                   std::cref(std::get<std::vector<Action>>(*eventData))));
448     if (std::get<TimerType>(tConf) == TimerType::repeating)
449     {
450         timer.restart(std::get<intervalPos>(tConf));
451     }
452     else if (std::get<TimerType>(tConf) == TimerType::oneshot)
453     {
454         timer.restartOnce(std::get<intervalPos>(tConf));
455     }
456     else
457     {
458         throw std::invalid_argument("Invalid Timer Type");
459     }
460     _timerEvents.emplace_back(std::move(eventData), std::move(timer));
461 }
462 
463 void Zone::timerExpired(const Group& eventGroup,
464                         const std::vector<Action>& eventActions)
465 {
466     // Perform the actions
467     std::for_each(eventActions.begin(),
468                   eventActions.end(),
469                   [this, &eventGroup](auto const& action)
470                   {
471                       action(*this, eventGroup);
472                   });
473 }
474 
475 void Zone::handleEvent(sdbusplus::message::message& msg,
476                        const EventData* eventData)
477 {
478     // Handle the callback
479     std::get<eventHandlerPos>(*eventData)(_bus, msg, *this);
480     // Perform the actions
481     std::for_each(
482         std::get<eventActionsPos>(*eventData).begin(),
483         std::get<eventActionsPos>(*eventData).end(),
484         [this, &eventData](auto const& action)
485         {
486             action(*this,
487                    std::get<eventGroupPos>(*eventData));
488         });
489 }
490 
491 const std::string& Zone::getService(const std::string& path,
492                                     const std::string& intf)
493 {
494     // Retrieve service from cache
495     auto srvIter = _servTree.find(path);
496     if (srvIter != _servTree.end())
497     {
498         for (auto& serv : srvIter->second)
499         {
500             auto it = std::find_if(
501                 serv.second.begin(),
502                 serv.second.end(),
503                 [&intf](auto const& interface)
504                 {
505                     return intf == interface;
506                 });
507             if (it != std::end(serv.second))
508             {
509                 // Service found
510                 return serv.first;
511             }
512         }
513         // Interface not found in cache, add and return
514         return addServices(path, intf, 0);
515     }
516     else
517     {
518         // Path not found in cache, add and return
519         return addServices(path, intf, 0);
520     }
521 }
522 
523 const std::string& Zone::addServices(const std::string& path,
524                                      const std::string& intf,
525                                      int32_t depth)
526 {
527     static const std::string empty = "";
528     auto it = _servTree.end();
529 
530     // Get all subtree objects for the given interface
531     auto objects = util::SDBusPlus::getSubTree(_bus, "/", intf, depth);
532     // Add what's returned to the cache of path->services
533     for (auto& pIter : objects)
534     {
535         auto pathIter = _servTree.find(pIter.first);
536         if (pathIter != _servTree.end())
537         {
538             // Path found in cache
539             for (auto& sIter : pIter.second)
540             {
541                 auto servIter = pathIter->second.find(sIter.first);
542                 if (servIter != pathIter->second.end())
543                 {
544                     // Service found in cache
545                     for (auto& iIter : sIter.second)
546                     {
547                         if (std::find(servIter->second.begin(),
548                                       servIter->second.end(),
549                                       iIter) == servIter->second.end())
550                         {
551                             // Add interface to cache
552                             servIter->second.emplace_back(iIter);
553                         }
554                     }
555                 }
556                 else
557                 {
558                     // Service not found in cache
559                     pathIter->second.insert(sIter);
560                 }
561             }
562         }
563         else
564         {
565             _servTree.insert(pIter);
566         }
567         // When the paths match, since a single interface constraint is given,
568         // that is the service to return
569         if (path == pIter.first)
570         {
571             it = _servTree.find(pIter.first);
572         }
573     }
574 
575     if (it != _servTree.end())
576     {
577         return it->second.begin()->first;
578     }
579 
580     return empty;
581 }
582 
583 auto Zone::getPersisted(const std::string& intf,
584                         const std::string& prop)
585 {
586     auto persisted = false;
587 
588     auto it = _persisted.find(intf);
589     if (it != _persisted.end())
590     {
591         return std::any_of(it->second.begin(),
592                            it->second.end(),
593                            [&prop](auto& p)
594                            {
595                                return prop == p;
596                            });
597     }
598 
599     return persisted;
600 }
601 
602 std::string Zone::current(std::string value)
603 {
604     auto current = ThermalObject::current();
605     std::transform(value.begin(), value.end(), value.begin(), toupper);
606 
607     auto supported = ThermalObject::supported();
608     auto isSupported = std::any_of(
609         supported.begin(),
610         supported.end(),
611         [&value](auto& s)
612         {
613             std::transform(s.begin(), s.end(), s.begin(), toupper);
614             return value == s;
615         });
616 
617     if (value != current && isSupported)
618     {
619         current = ThermalObject::current(value);
620         if (getPersisted("xyz.openbmc_project.Control.ThermalMode", "Current"))
621         {
622             saveCurrentMode();
623         }
624         // Trigger event(s) for current mode property change
625         auto eData = _objects[_path]
626                              ["xyz.openbmc_project.Control.ThermalMode"]
627                              ["Current"];
628         if (eData != nullptr)
629         {
630             sdbusplus::message::message nullMsg{nullptr};
631             handleEvent(nullMsg, eData);
632         }
633     }
634 
635     return current;
636 }
637 
638 void Zone::saveCurrentMode()
639 {
640     fs::path path{CONTROL_PERSIST_ROOT_PATH};
641     // Append zone and property description
642     path /= std::to_string(_zoneNum);
643     path /= "CurrentMode";
644     std::ofstream ofs(path.c_str(), std::ios::binary);
645     cereal::JSONOutputArchive oArch(ofs);
646     oArch(ThermalObject::current());
647 }
648 
649 void Zone::restoreCurrentMode()
650 {
651     auto current = ThermalObject::current();
652     fs::path path{CONTROL_PERSIST_ROOT_PATH};
653     path /= std::to_string(_zoneNum);
654     path /= "CurrentMode";
655     fs::create_directories(path.parent_path());
656 
657     try
658     {
659         if (fs::exists(path))
660         {
661             std::ifstream ifs(path.c_str(), std::ios::in | std::ios::binary);
662             cereal::JSONInputArchive iArch(ifs);
663             iArch(current);
664         }
665     }
666     catch (std::exception& e)
667     {
668         log<level::ERR>(e.what());
669         fs::remove(path);
670         current = ThermalObject::current();
671     }
672 
673     this->current(current);
674 }
675 
676 }
677 }
678 }
679