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 
Zone(Mode mode,sdbusplus::bus_t & bus,const std::string & path,const sdeventplus::Event & event,const ZoneDefinition & def)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 
setSpeed(uint64_t speed)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 
setFullSpeed()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 
setActiveAllow(const Group * group,bool isActiveAllow)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 = [](const auto& entry) { return entry.second; };
136         _isActive = std::all_of(_active.begin(), _active.end(), actPred);
137     }
138 }
139 
removeService(const Group * group,const std::string & name)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](const auto& 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 
setServiceOwner(const Group * group,const std::string & name,const bool hasOwner)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](const auto& 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 
setServices(const Group * group)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 
setFloor(uint64_t speed)210 void Zone::setFloor(uint64_t speed)
211 {
212     // Check all entries are set to allow floor to be set
213     auto pred = [](const auto& 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 
requestSpeedIncrease(uint64_t targetDelta)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 
incTimerExpired()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 
requestSpeedDecrease(uint64_t targetDelta)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 
decTimerExpired()262 void Zone::decTimerExpired()
263 {
264     // Check all entries are set to allow a decrease
265     auto pred = [](const auto& 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 
initEvent(const SetSpeedEvent & event)299 void Zone::initEvent(const SetSpeedEvent& event)
300 {
301     // Enable event triggers
302     std::for_each(std::get<triggerPos>(event).begin(),
303                   std::get<triggerPos>(event).end(),
304                   [this, &event](const auto& trigger) {
305         if (!std::get<actionsPos>(event).empty())
306         {
307             std::for_each(std::get<actionsPos>(event).begin(),
308                           std::get<actionsPos>(event).end(),
309                           [this, &trigger, &event](auto const& action) {
310                 // Default to use group defined with action if exists
311                 if (!std::get<adGroupPos>(action).empty())
312                 {
313                     trigger(*this, std::get<sseNamePos>(event),
314                             std::get<adGroupPos>(action),
315                             std::get<adActionsPos>(action));
316                 }
317                 else
318                 {
319                     trigger(*this, std::get<sseNamePos>(event),
320                             std::get<groupPos>(event),
321                             std::get<adActionsPos>(action));
322                 }
323             });
324         }
325         else
326         {
327             trigger(*this, std::get<sseNamePos>(event),
328                     std::get<groupPos>(event), {});
329         }
330     });
331 }
332 
removeEvent(const SetSpeedEvent & event)333 void Zone::removeEvent(const SetSpeedEvent& event)
334 {
335     // Remove event signals
336     auto sigIter = _signalEvents.find(std::get<sseNamePos>(event));
337     if (sigIter != _signalEvents.end())
338     {
339         auto& signals = sigIter->second;
340         for (auto it = signals.begin(); it != signals.end(); ++it)
341         {
342             removeSignal(it);
343         }
344         _signalEvents.erase(sigIter);
345     }
346 
347     // Remove event timers
348     auto timIter = _timerEvents.find(std::get<sseNamePos>(event));
349     if (timIter != _timerEvents.end())
350     {
351         _timerEvents.erase(timIter);
352     }
353 }
354 
355 std::vector<TimerEvent>::iterator
findTimer(const Group & eventGroup,const std::vector<Action> & eventActions,std::vector<TimerEvent> & eventTimers)356     Zone::findTimer(const Group& eventGroup,
357                     const std::vector<Action>& eventActions,
358                     std::vector<TimerEvent>& eventTimers)
359 {
360     for (auto it = eventTimers.begin(); it != eventTimers.end(); ++it)
361     {
362         const auto& teEventData = *std::get<timerEventDataPos>(*it);
363         if (std::get<eventGroupPos>(teEventData) == eventGroup &&
364             std::get<eventActionsPos>(teEventData).size() ==
365                 eventActions.size())
366         {
367             // TODO openbmc/openbmc#2328 - Use the action function target
368             // for comparison
369             auto actsEqual = [](const auto& a1, const auto& a2) {
370                 return a1.target_type().name() == a2.target_type().name();
371             };
372             if (std::equal(eventActions.begin(), eventActions.end(),
373                            std::get<eventActionsPos>(teEventData).begin(),
374                            actsEqual))
375             {
376                 return it;
377             }
378         }
379     }
380 
381     return eventTimers.end();
382 }
383 
addTimer(const std::string & name,const Group & group,const std::vector<Action> & actions,const TimerConf & tConf)384 void Zone::addTimer(const std::string& name, const Group& group,
385                     const std::vector<Action>& actions, const TimerConf& tConf)
386 {
387     auto eventData = std::make_unique<EventData>(group, "", nullptr, actions);
388     Timer timer(
389         _eventLoop,
390         std::bind(&Zone::timerExpired, this,
391                   std::cref(std::get<Group>(*eventData)),
392                   std::cref(std::get<std::vector<Action>>(*eventData))));
393     if (std::get<TimerType>(tConf) == TimerType::repeating)
394     {
395         timer.restart(std::get<intervalPos>(tConf));
396     }
397     else if (std::get<TimerType>(tConf) == TimerType::oneshot)
398     {
399         timer.restartOnce(std::get<intervalPos>(tConf));
400     }
401     else
402     {
403         throw std::invalid_argument("Invalid Timer Type");
404     }
405     _timerEvents[name].emplace_back(std::move(eventData), std::move(timer));
406 }
407 
timerExpired(const Group & eventGroup,const std::vector<Action> & eventActions)408 void Zone::timerExpired(const Group& eventGroup,
409                         const std::vector<Action>& eventActions)
410 {
411     // Perform the actions
412     std::for_each(
413         eventActions.begin(), eventActions.end(),
414         [this, &eventGroup](const auto& action) { action(*this, eventGroup); });
415 }
416 
handleEvent(sdbusplus::message_t & msg,const EventData * eventData)417 void Zone::handleEvent(sdbusplus::message_t& msg, const EventData* eventData)
418 {
419     // Handle the callback
420     std::get<eventHandlerPos> (*eventData)(_bus, msg, *this);
421     // Perform the actions
422     std::for_each(std::get<eventActionsPos>(*eventData).begin(),
423                   std::get<eventActionsPos>(*eventData).end(),
424                   [this, &eventData](const auto& action) {
425         action(*this, std::get<eventGroupPos>(*eventData));
426     });
427 }
428 
getService(const std::string & path,const std::string & intf)429 const std::string& Zone::getService(const std::string& path,
430                                     const std::string& intf)
431 {
432     // Retrieve service from cache
433     auto srvIter = _servTree.find(path);
434     if (srvIter != _servTree.end())
435     {
436         for (auto& serv : srvIter->second)
437         {
438             auto it = std::find_if(
439                 serv.second.begin(), serv.second.end(),
440                 [&intf](const auto& interface) { return intf == interface; });
441             if (it != std::end(serv.second))
442             {
443                 // Service found
444                 return serv.first;
445             }
446         }
447         // Interface not found in cache, add and return
448         return addServices(path, intf, 0);
449     }
450     else
451     {
452         // Path not found in cache, add and return
453         return addServices(path, intf, 0);
454     }
455 }
456 
addServices(const std::string & path,const std::string & intf,int32_t depth)457 const std::string& Zone::addServices(const std::string& path,
458                                      const std::string& intf, int32_t depth)
459 {
460     static const std::string empty = "";
461     auto it = _servTree.end();
462 
463     // Get all subtree objects for the given interface
464     auto objects = util::SDBusPlus::getSubTree(_bus, "/", intf, depth);
465     // Add what's returned to the cache of path->services
466     for (auto& pIter : objects)
467     {
468         auto pathIter = _servTree.find(pIter.first);
469         if (pathIter != _servTree.end())
470         {
471             // Path found in cache
472             for (auto& sIter : pIter.second)
473             {
474                 auto servIter = pathIter->second.find(sIter.first);
475                 if (servIter != pathIter->second.end())
476                 {
477                     // Service found in cache
478                     for (auto& iIter : sIter.second)
479                     {
480                         if (std::find(servIter->second.begin(),
481                                       servIter->second.end(),
482                                       iIter) == servIter->second.end())
483                         {
484                             // Add interface to cache
485                             servIter->second.emplace_back(iIter);
486                         }
487                     }
488                 }
489                 else
490                 {
491                     // Service not found in cache
492                     pathIter->second.insert(sIter);
493                 }
494             }
495         }
496         else
497         {
498             _servTree.insert(pIter);
499         }
500         // When the paths match, since a single interface constraint is given,
501         // that is the service to return
502         if (path == pIter.first)
503         {
504             it = _servTree.find(pIter.first);
505         }
506     }
507 
508     if (it != _servTree.end())
509     {
510         return it->second.begin()->first;
511     }
512 
513     return empty;
514 }
515 
getPersisted(const std::string & intf,const std::string & prop)516 auto Zone::getPersisted(const std::string& intf, const std::string& prop)
517 {
518     auto persisted = false;
519 
520     auto it = _persisted.find(intf);
521     if (it != _persisted.end())
522     {
523         return std::any_of(it->second.begin(), it->second.end(),
524                            [&prop](const auto& p) { return prop == p; });
525     }
526 
527     return persisted;
528 }
529 
current(std::string value)530 std::string Zone::current(std::string value)
531 {
532     auto current = ThermalObject::current();
533     std::transform(value.begin(), value.end(), value.begin(), toupper);
534 
535     auto supported = ThermalObject::supported();
536     auto isSupported = std::any_of(supported.begin(), supported.end(),
537                                    [&value](auto& s) {
538         std::transform(s.begin(), s.end(), s.begin(), toupper);
539         return value == s;
540     });
541 
542     if (value != current && isSupported)
543     {
544         current = ThermalObject::current(value);
545         if (getPersisted("xyz.openbmc_project.Control.ThermalMode", "Current"))
546         {
547             saveCurrentMode();
548         }
549         // Trigger event(s) for current mode property change
550         auto eData = _objects[_path]["xyz.openbmc_project.Control.ThermalMode"]
551                              ["Current"];
552         if (eData != nullptr)
553         {
554             sdbusplus::message_t nullMsg{nullptr};
555             handleEvent(nullMsg, eData);
556         }
557     }
558 
559     return current;
560 }
561 
saveCurrentMode()562 void Zone::saveCurrentMode()
563 {
564     fs::path path{CONTROL_PERSIST_ROOT_PATH};
565     // Append zone and property description
566     path /= std::to_string(_zoneNum);
567     path /= "CurrentMode";
568     std::ofstream ofs(path.c_str(), std::ios::binary);
569     cereal::JSONOutputArchive oArch(ofs);
570     oArch(ThermalObject::current());
571 }
572 
restoreCurrentMode()573 void Zone::restoreCurrentMode()
574 {
575     auto current = ThermalObject::current();
576     fs::path path{CONTROL_PERSIST_ROOT_PATH};
577     path /= std::to_string(_zoneNum);
578     path /= "CurrentMode";
579     fs::create_directories(path.parent_path());
580 
581     try
582     {
583         if (fs::exists(path))
584         {
585             std::ifstream ifs(path.c_str(), std::ios::in | std::ios::binary);
586             cereal::JSONInputArchive iArch(ifs);
587             iArch(current);
588         }
589     }
590     catch (const std::exception& e)
591     {
592         log<level::ERR>(e.what());
593         fs::remove(path);
594         current = ThermalObject::current();
595     }
596 
597     this->current(current);
598 }
599 
600 } // namespace control
601 } // namespace fan
602 } // namespace phosphor
603