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