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