1 /**
2  * Copyright © 2020 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 "zone.hpp"
17 
18 #include "dbus_zone.hpp"
19 #include "fan.hpp"
20 #include "sdbusplus.hpp"
21 
22 #include <nlohmann/json.hpp>
23 #include <phosphor-logging/log.hpp>
24 #include <sdeventplus/event.hpp>
25 
26 #include <algorithm>
27 #include <chrono>
28 #include <iterator>
29 #include <map>
30 #include <memory>
31 #include <numeric>
32 #include <utility>
33 #include <vector>
34 
35 namespace phosphor::fan::control::json
36 {
37 
38 using json = nlohmann::json;
39 using namespace phosphor::logging;
40 
41 const std::map<
42     std::string,
43     std::map<std::string, std::function<std::function<void(DBusZone&, Zone&)>(
44                               const json&, bool)>>>
45     Zone::_intfPropHandlers = {
46         {DBusZone::thermalModeIntf,
47          {{DBusZone::supportedProp, zone::property::supported},
48           {DBusZone::currentProp, zone::property::current}}}};
49 
50 Zone::Zone(const json& jsonObj, const sdeventplus::Event& event, Manager* mgr) :
51     ConfigBase(jsonObj), _dbusZone{}, _manager(mgr), _defaultFloor(0),
52     _incDelay(0), _decInterval(0), _floor(0), _target(0), _incDelta(0),
53     _decDelta(0), _requestTargetBase(0), _isActive(true),
54     _incTimer(event, std::bind(&Zone::incTimerExpired, this)),
55     _decTimer(event, std::bind(&Zone::decTimerExpired, this))
56 {
57     // Increase delay is optional, defaults to 0
58     if (jsonObj.contains("increase_delay"))
59     {
60         _incDelay =
61             std::chrono::seconds(jsonObj["increase_delay"].get<uint64_t>());
62     }
63 
64     // Poweron target is required
65     setPowerOnTarget(jsonObj);
66 
67     // Default ceiling is optional, defaults to poweron target
68     _defaultCeiling = _poweronTarget;
69     if (jsonObj.contains("default_ceiling"))
70     {
71         _defaultCeiling = jsonObj["default_ceiling"].get<uint64_t>();
72     }
73     // Start with the current ceiling set as the default ceiling
74     _ceiling = _defaultCeiling;
75 
76     // Default floor is optional, defaults to 0
77     if (jsonObj.contains("default_floor"))
78     {
79         _defaultFloor = jsonObj["default_floor"].get<uint64_t>();
80         // Start with the current floor set as the default
81         _floor = _defaultFloor;
82     }
83 
84     // Decrease interval is optional, defaults to 0
85     // A decrease interval of 0sec disables the decrease timer
86     if (jsonObj.contains("decrease_interval"))
87     {
88         _decInterval =
89             std::chrono::seconds(jsonObj["decrease_interval"].get<uint64_t>());
90     }
91 
92     // Setting properties on interfaces to be served are optional
93     if (jsonObj.contains("interfaces"))
94     {
95         setInterfaces(jsonObj);
96     }
97 }
98 
99 void Zone::enable()
100 {
101     // Create thermal control dbus object
102     _dbusZone = std::make_unique<DBusZone>(*this);
103 
104     // Init all configured dbus interfaces' property states
105     for (const auto& func : _propInitFunctions)
106     {
107         // Only call non-null init property functions
108         if (func)
109         {
110             func(*_dbusZone, *this);
111         }
112     }
113 
114     // TODO - Restore any persisted properties in init function
115     // Restore thermal control current mode state, if exists
116     _dbusZone->restoreCurrentMode();
117 
118     // Emit object added for this zone's associated dbus object
119     _dbusZone->emit_object_added();
120 
121     // A decrease interval of 0sec disables the decrease timer
122     if (_decInterval != std::chrono::seconds::zero())
123     {
124         // Start timer for fan target decreases
125         _decTimer.restart(_decInterval);
126     }
127 }
128 
129 void Zone::addFan(std::unique_ptr<Fan> fan)
130 {
131     _fans.emplace_back(std::move(fan));
132 }
133 
134 void Zone::setTarget(uint64_t target)
135 {
136     if (_isActive)
137     {
138         _target = target;
139         for (auto& fan : _fans)
140         {
141             fan->setTarget(_target);
142         }
143     }
144 }
145 
146 void Zone::setTargetHold(const std::string& ident, uint64_t target, bool hold)
147 {
148     if (!hold)
149     {
150         _holds.erase(ident);
151     }
152     else
153     {
154         _holds[ident] = target;
155         _isActive = false;
156     }
157 
158     auto itHoldMax = std::max_element(_holds.begin(), _holds.end(),
159                                       [](const auto& aHold, const auto& bHold) {
160                                           return aHold.second < bHold.second;
161                                       });
162     if (itHoldMax == _holds.end())
163     {
164         _isActive = true;
165     }
166     else
167     {
168         _target = itHoldMax->second;
169         for (auto& fan : _fans)
170         {
171             fan->setTarget(_target);
172         }
173     }
174 }
175 
176 void Zone::setFloor(uint64_t target)
177 {
178     // Check all entries are set to allow floor to be set
179     auto pred = [](const auto& entry) { return entry.second; };
180     if (std::all_of(_floorChange.begin(), _floorChange.end(), pred))
181     {
182         _floor = target;
183         // Floor above target, update target to floor
184         if (_target < _floor)
185         {
186             requestIncrease(_floor - _target);
187         }
188     }
189 }
190 
191 void Zone::requestIncrease(uint64_t targetDelta)
192 {
193     // Only increase when delta is higher than the current increase delta for
194     // the zone and currently under ceiling
195     if (targetDelta > _incDelta && _target < _ceiling)
196     {
197         auto requestTarget = getRequestTargetBase();
198         requestTarget = (targetDelta - _incDelta) + requestTarget;
199         _incDelta = targetDelta;
200         // Target can not go above a current ceiling
201         if (requestTarget > _ceiling)
202         {
203             requestTarget = _ceiling;
204         }
205         setTarget(requestTarget);
206         // Restart timer countdown for fan target increase
207         _incTimer.restartOnce(_incDelay);
208     }
209 }
210 
211 void Zone::incTimerExpired()
212 {
213     // Clear increase delta when timer expires allowing additional target
214     // increase requests or target decreases to occur
215     _incDelta = 0;
216 }
217 
218 void Zone::requestDecrease(uint64_t targetDelta)
219 {
220     // Only decrease the lowest target delta requested
221     if (_decDelta == 0 || targetDelta < _decDelta)
222     {
223         _decDelta = targetDelta;
224     }
225 }
226 
227 void Zone::decTimerExpired()
228 {
229     // Check all entries are set to allow a decrease
230     auto pred = [](auto const& entry) { return entry.second; };
231     auto decAllowed = std::all_of(_decAllowed.begin(), _decAllowed.end(), pred);
232 
233     // Only decrease targets when allowed, a requested decrease target delta
234     // exists, where no requested increases exist and the increase timer is not
235     // running (i.e. not in the middle of increasing)
236     if (decAllowed && _decDelta != 0 && _incDelta == 0 &&
237         !_incTimer.isEnabled())
238     {
239         auto requestTarget = getRequestTargetBase();
240         // Request target should not start above ceiling
241         if (requestTarget > _ceiling)
242         {
243             requestTarget = _ceiling;
244         }
245         // Target can not go below the defined floor
246         if ((requestTarget < _decDelta) || (requestTarget - _decDelta < _floor))
247         {
248             requestTarget = _floor;
249         }
250         else
251         {
252             requestTarget = requestTarget - _decDelta;
253         }
254         setTarget(requestTarget);
255     }
256     // Clear decrease delta when timer expires
257     _decDelta = 0;
258     // Decrease timer is restarted since its repeating
259 }
260 
261 void Zone::setPersisted(const std::string& intf, const std::string& prop)
262 {
263     if (std::find_if(_propsPersisted[intf].begin(), _propsPersisted[intf].end(),
264                      [&prop](const auto& p) { return prop == p; }) ==
265         _propsPersisted[intf].end())
266     {
267         _propsPersisted[intf].emplace_back(prop);
268     }
269 }
270 
271 bool Zone::isPersisted(const std::string& intf, const std::string& prop) const
272 {
273     auto it = _propsPersisted.find(intf);
274     if (it == _propsPersisted.end())
275     {
276         return false;
277     }
278 
279     return std::any_of(it->second.begin(), it->second.end(),
280                        [&prop](const auto& p) { return prop == p; });
281 }
282 
283 void Zone::setPowerOnTarget(const json& jsonObj)
284 {
285     if (!jsonObj.contains("poweron_target"))
286     {
287         auto msg = "Missing required zone's poweron target";
288         log<level::ERR>(msg, entry("JSON=%s", jsonObj.dump().c_str()));
289         throw std::runtime_error(msg);
290     }
291     _poweronTarget = jsonObj["poweron_target"].get<uint64_t>();
292 }
293 
294 void Zone::setInterfaces(const json& jsonObj)
295 {
296     for (const auto& interface : jsonObj["interfaces"])
297     {
298         if (!interface.contains("name") || !interface.contains("properties"))
299         {
300             log<level::ERR>("Missing required zone interface attributes",
301                             entry("JSON=%s", interface.dump().c_str()));
302             throw std::runtime_error(
303                 "Missing required zone interface attributes");
304         }
305         auto propFuncs =
306             _intfPropHandlers.find(interface["name"].get<std::string>());
307         if (propFuncs == _intfPropHandlers.end())
308         {
309             // Construct list of available configurable interfaces
310             auto intfs = std::accumulate(
311                 std::next(_intfPropHandlers.begin()), _intfPropHandlers.end(),
312                 _intfPropHandlers.begin()->first, [](auto list, auto intf) {
313                     return std::move(list) + ", " + intf.first;
314                 });
315             log<level::ERR>("Configured interface not available",
316                             entry("JSON=%s", interface.dump().c_str()),
317                             entry("AVAILABLE_INTFS=%s", intfs.c_str()));
318             throw std::runtime_error("Configured interface not available");
319         }
320 
321         for (const auto& property : interface["properties"])
322         {
323             if (!property.contains("name"))
324             {
325                 log<level::ERR>(
326                     "Missing required interface property attributes",
327                     entry("JSON=%s", property.dump().c_str()));
328                 throw std::runtime_error(
329                     "Missing required interface property attributes");
330             }
331             // Attribute "persist" is optional, defaults to `false`
332             auto persist = false;
333             if (property.contains("persist"))
334             {
335                 persist = property["persist"].get<bool>();
336             }
337             // Property name from JSON must exactly match supported
338             // index names to functions in property namespace
339             auto propFunc =
340                 propFuncs->second.find(property["name"].get<std::string>());
341             if (propFunc == propFuncs->second.end())
342             {
343                 // Construct list of available configurable properties
344                 auto props = std::accumulate(
345                     std::next(propFuncs->second.begin()),
346                     propFuncs->second.end(), propFuncs->second.begin()->first,
347                     [](auto list, auto prop) {
348                         return std::move(list) + ", " + prop.first;
349                     });
350                 log<level::ERR>("Configured property not available",
351                                 entry("JSON=%s", property.dump().c_str()),
352                                 entry("AVAILABLE_PROPS=%s", props.c_str()));
353                 throw std::runtime_error(
354                     "Configured property function not available");
355             }
356 
357             _propInitFunctions.emplace_back(
358                 propFunc->second(property, persist));
359         }
360     }
361 }
362 
363 /**
364  * Properties of interfaces supported by the zone configuration that return
365  * a handler function that sets the zone's property value(s) and persist
366  * state.
367  */
368 namespace zone::property
369 {
370 // Get a set property handler function for the configured values of the
371 // "Supported" property
372 std::function<void(DBusZone&, Zone&)> supported(const json& jsonObj,
373                                                 bool persist)
374 {
375     std::vector<std::string> values;
376     if (!jsonObj.contains("values"))
377     {
378         log<level::ERR>("No 'values' found for \"Supported\" property, "
379                         "using an empty list",
380                         entry("JSON=%s", jsonObj.dump().c_str()));
381     }
382     else
383     {
384         for (const auto& value : jsonObj["values"])
385         {
386             if (!value.contains("value"))
387             {
388                 log<level::ERR>("No 'value' found for \"Supported\" property "
389                                 "entry, skipping",
390                                 entry("JSON=%s", value.dump().c_str()));
391             }
392             else
393             {
394                 values.emplace_back(value["value"].get<std::string>());
395             }
396         }
397     }
398 
399     return Zone::setProperty<std::vector<std::string>>(
400         DBusZone::thermalModeIntf, DBusZone::supportedProp,
401         &DBusZone::supported, std::move(values), persist);
402 }
403 
404 // Get a set property handler function for a configured value of the
405 // "Current" property
406 std::function<void(DBusZone&, Zone&)> current(const json& jsonObj, bool persist)
407 {
408     // Use default value for "Current" property if no "value" entry given
409     if (!jsonObj.contains("value"))
410     {
411         log<level::INFO>("No 'value' found for \"Current\" property, "
412                          "using default",
413                          entry("JSON=%s", jsonObj.dump().c_str()));
414         // Set persist state of property
415         return Zone::setPropertyPersist(DBusZone::thermalModeIntf,
416                                         DBusZone::currentProp, persist);
417     }
418 
419     return Zone::setProperty<std::string>(
420         DBusZone::thermalModeIntf, DBusZone::currentProp, &DBusZone::current,
421         jsonObj["value"].get<std::string>(), persist);
422 }
423 
424 } // namespace zone::property
425 
426 } // namespace phosphor::fan::control::json
427