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