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::setActiveAllow(const std::string& ident, bool isActiveAllow)
147 {
148     _active[ident] = isActiveAllow;
149     if (!isActiveAllow)
150     {
151         _isActive = false;
152     }
153     else
154     {
155         // Check all entries are set to allow active fan control
156         auto actPred = [](const auto& entry) { return entry.second; };
157         _isActive = std::all_of(_active.begin(), _active.end(), actPred);
158     }
159 }
160 
161 void Zone::setFloor(uint64_t target)
162 {
163     // Check all entries are set to allow floor to be set
164     auto pred = [](const auto& entry) { return entry.second; };
165     if (std::all_of(_floorChange.begin(), _floorChange.end(), pred))
166     {
167         _floor = target;
168         // Floor above target, update target to floor
169         if (_target < _floor)
170         {
171             requestIncrease(_floor - _target);
172         }
173     }
174 }
175 
176 void Zone::requestIncrease(uint64_t targetDelta)
177 {
178     // Only increase when delta is higher than the current increase delta for
179     // the zone and currently under ceiling
180     if (targetDelta > _incDelta && _target < _ceiling)
181     {
182         auto requestTarget = getRequestTargetBase();
183         requestTarget = (targetDelta - _incDelta) + requestTarget;
184         _incDelta = targetDelta;
185         // Target can not go above a current ceiling
186         if (requestTarget > _ceiling)
187         {
188             requestTarget = _ceiling;
189         }
190         setTarget(requestTarget);
191         // Restart timer countdown for fan target increase
192         _incTimer.restartOnce(_incDelay);
193     }
194 }
195 
196 void Zone::incTimerExpired()
197 {
198     // Clear increase delta when timer expires allowing additional target
199     // increase requests or target decreases to occur
200     _incDelta = 0;
201 }
202 
203 void Zone::requestDecrease(uint64_t targetDelta)
204 {
205     // Only decrease the lowest target delta requested
206     if (_decDelta == 0 || targetDelta < _decDelta)
207     {
208         _decDelta = targetDelta;
209     }
210 }
211 
212 void Zone::decTimerExpired()
213 {
214     // Check all entries are set to allow a decrease
215     auto pred = [](auto const& entry) { return entry.second; };
216     auto decAllowed = std::all_of(_decAllowed.begin(), _decAllowed.end(), pred);
217 
218     // Only decrease targets when allowed, a requested decrease target delta
219     // exists, where no requested increases exist and the increase timer is not
220     // running (i.e. not in the middle of increasing)
221     if (decAllowed && _decDelta != 0 && _incDelta == 0 &&
222         !_incTimer.isEnabled())
223     {
224         auto requestTarget = getRequestTargetBase();
225         // Request target should not start above ceiling
226         if (requestTarget > _ceiling)
227         {
228             requestTarget = _ceiling;
229         }
230         // Target can not go below the defined floor
231         if ((requestTarget < _decDelta) || (requestTarget - _decDelta < _floor))
232         {
233             requestTarget = _floor;
234         }
235         else
236         {
237             requestTarget = requestTarget - _decDelta;
238         }
239         setTarget(requestTarget);
240     }
241     // Clear decrease delta when timer expires
242     _decDelta = 0;
243     // Decrease timer is restarted since its repeating
244 }
245 
246 void Zone::setPersisted(const std::string& intf, const std::string& prop)
247 {
248     if (std::find_if(_propsPersisted[intf].begin(), _propsPersisted[intf].end(),
249                      [&prop](const auto& p) { return prop == p; }) ==
250         _propsPersisted[intf].end())
251     {
252         _propsPersisted[intf].emplace_back(prop);
253     }
254 }
255 
256 bool Zone::isPersisted(const std::string& intf, const std::string& prop) const
257 {
258     auto it = _propsPersisted.find(intf);
259     if (it == _propsPersisted.end())
260     {
261         return false;
262     }
263 
264     return std::any_of(it->second.begin(), it->second.end(),
265                        [&prop](const auto& p) { return prop == p; });
266 }
267 
268 void Zone::setPowerOnTarget(const json& jsonObj)
269 {
270     // TODO Remove "full_speed" after configs replaced with "poweron_target"
271     if (!jsonObj.contains("full_speed") && !jsonObj.contains("poweron_target"))
272     {
273         auto msg = "Missing required zone's poweron target";
274         log<level::ERR>(msg, entry("JSON=%s", jsonObj.dump().c_str()));
275         throw std::runtime_error(msg);
276     }
277     if (jsonObj.contains("full_speed"))
278     {
279         _poweronTarget = jsonObj["full_speed"].get<uint64_t>();
280     }
281     else
282     {
283         _poweronTarget = jsonObj["poweron_target"].get<uint64_t>();
284     }
285     // Start with the current target set as the poweron target
286     _target = _poweronTarget;
287 }
288 
289 void Zone::setInterfaces(const json& jsonObj)
290 {
291     for (const auto& interface : jsonObj["interfaces"])
292     {
293         if (!interface.contains("name") || !interface.contains("properties"))
294         {
295             log<level::ERR>("Missing required zone interface attributes",
296                             entry("JSON=%s", interface.dump().c_str()));
297             throw std::runtime_error(
298                 "Missing required zone interface attributes");
299         }
300         auto propFuncs =
301             _intfPropHandlers.find(interface["name"].get<std::string>());
302         if (propFuncs == _intfPropHandlers.end())
303         {
304             // Construct list of available configurable interfaces
305             auto intfs = std::accumulate(
306                 std::next(_intfPropHandlers.begin()), _intfPropHandlers.end(),
307                 _intfPropHandlers.begin()->first, [](auto list, auto intf) {
308                     return std::move(list) + ", " + intf.first;
309                 });
310             log<level::ERR>("Configured interface not available",
311                             entry("JSON=%s", interface.dump().c_str()),
312                             entry("AVAILABLE_INTFS=%s", intfs.c_str()));
313             throw std::runtime_error("Configured interface not available");
314         }
315 
316         for (const auto& property : interface["properties"])
317         {
318             if (!property.contains("name"))
319             {
320                 log<level::ERR>(
321                     "Missing required interface property attributes",
322                     entry("JSON=%s", property.dump().c_str()));
323                 throw std::runtime_error(
324                     "Missing required interface property attributes");
325             }
326             // Attribute "persist" is optional, defaults to `false`
327             auto persist = false;
328             if (property.contains("persist"))
329             {
330                 persist = property["persist"].get<bool>();
331             }
332             // Property name from JSON must exactly match supported
333             // index names to functions in property namespace
334             auto propFunc =
335                 propFuncs->second.find(property["name"].get<std::string>());
336             if (propFunc == propFuncs->second.end())
337             {
338                 // Construct list of available configurable properties
339                 auto props = std::accumulate(
340                     std::next(propFuncs->second.begin()),
341                     propFuncs->second.end(), propFuncs->second.begin()->first,
342                     [](auto list, auto prop) {
343                         return std::move(list) + ", " + prop.first;
344                     });
345                 log<level::ERR>("Configured property not available",
346                                 entry("JSON=%s", property.dump().c_str()),
347                                 entry("AVAILABLE_PROPS=%s", props.c_str()));
348                 throw std::runtime_error(
349                     "Configured property function not available");
350             }
351 
352             _propInitFunctions.emplace_back(
353                 propFunc->second(property, persist));
354         }
355     }
356 }
357 
358 /**
359  * Properties of interfaces supported by the zone configuration that return
360  * a handler function that sets the zone's property value(s) and persist
361  * state.
362  */
363 namespace zone::property
364 {
365 // Get a set property handler function for the configured values of the
366 // "Supported" property
367 std::function<void(DBusZone&, Zone&)> supported(const json& jsonObj,
368                                                 bool persist)
369 {
370     std::vector<std::string> values;
371     if (!jsonObj.contains("values"))
372     {
373         log<level::ERR>("No 'values' found for \"Supported\" property, "
374                         "using an empty list",
375                         entry("JSON=%s", jsonObj.dump().c_str()));
376     }
377     else
378     {
379         for (const auto& value : jsonObj["values"])
380         {
381             if (!value.contains("value"))
382             {
383                 log<level::ERR>("No 'value' found for \"Supported\" property "
384                                 "entry, skipping",
385                                 entry("JSON=%s", value.dump().c_str()));
386             }
387             else
388             {
389                 values.emplace_back(value["value"].get<std::string>());
390             }
391         }
392     }
393 
394     return Zone::setProperty<std::vector<std::string>>(
395         DBusZone::thermalModeIntf, DBusZone::supportedProp,
396         &DBusZone::supported, std::move(values), persist);
397 }
398 
399 // Get a set property handler function for a configured value of the
400 // "Current" property
401 std::function<void(DBusZone&, Zone&)> current(const json& jsonObj, bool persist)
402 {
403     // Use default value for "Current" property if no "value" entry given
404     if (!jsonObj.contains("value"))
405     {
406         log<level::INFO>("No 'value' found for \"Current\" property, "
407                          "using default",
408                          entry("JSON=%s", jsonObj.dump().c_str()));
409         // Set persist state of property
410         return Zone::setPropertyPersist(DBusZone::thermalModeIntf,
411                                         DBusZone::currentProp, persist);
412     }
413 
414     return Zone::setProperty<std::string>(
415         DBusZone::thermalModeIntf, DBusZone::currentProp, &DBusZone::current,
416         jsonObj["value"].get<std::string>(), persist);
417 }
418 
419 } // namespace zone::property
420 
421 } // namespace phosphor::fan::control::json
422