1 /**
2  * Copyright © 2022 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 #pragma once
17 
18 #include "config_base.hpp"
19 #include "dbus_zone.hpp"
20 #include "fan.hpp"
21 
22 #include <nlohmann/json.hpp>
23 #include <sdeventplus/event.hpp>
24 #include <sdeventplus/utility/timer.hpp>
25 
26 #include <any>
27 #include <chrono>
28 #include <functional>
29 #include <map>
30 #include <memory>
31 #include <tuple>
32 
33 namespace phosphor::fan::control::json
34 {
35 
36 class Manager;
37 
38 using json = nlohmann::json;
39 
40 /* Dbus event timer */
41 using Timer = sdeventplus::utility::Timer<sdeventplus::ClockId::Monotonic>;
42 
43 /**
44  * @class Zone - Represents a configured fan control zone
45  *
46  * A zone object contains the configured attributes for a zone that groups
47  * a number of fans together to be under the same target control. These
48  * configuration attributes include, but are not limited to, the default ceiling
49  * of the fans within the zone, a default floor, the delay between increases, a
50  * decrease interval, and any profiles(OPTIONAL) the zone should be included in.
51  *
52  * (When no profile for a zone is given, the zone defaults to always exist)
53  *
54  */
55 class Zone : public ConfigBase
56 {
57   public:
58     /* JSON file name for zones */
59     static constexpr auto confFileName = "zones.json";
60 
61     Zone() = delete;
62     Zone(const Zone&) = delete;
63     Zone(Zone&&) = delete;
64     Zone& operator=(const Zone&) = delete;
65     Zone& operator=(Zone&&) = delete;
66     ~Zone() = default;
67 
68     /**
69      * Constructor
70      * Parses and populates a zone from JSON object data
71      *
72      * @param[in] jsonObj - JSON object
73      * @param[in] event - sdeventplus event loop
74      * @param[in] mgr - Manager of this zone
75      */
76     Zone(const json& jsonObj, const sdeventplus::Event& event, Manager* mgr);
77 
78     /**
79      * @brief Get the poweron target
80      *
81      * Poweron target is the target the fans within this zone should be set to
82      * when the system is powered on.
83      *
84      * @return Poweron target of this zone
85      */
getPoweronTarget() const86     inline const auto& getPoweronTarget() const
87     {
88         return _poweronTarget;
89     }
90 
91     /**
92      * @brief Get the default ceiling
93      *
94      * Default ceiling is the highest target the fans within this zone is
95      * allowed to increase to. The zone's ceiling defaults to this unless
96      * changed by some configured event.
97      *
98      * @return Default ceiling of this zone
99      */
getDefaultCeiling() const100     inline const auto& getDefaultCeiling() const
101     {
102         return _defaultCeiling;
103     }
104 
105     /**
106      * @brief Get the default floor
107      *
108      * The default floor is the lowest target the fans within this zone
109      * are allowed to decrease to. The zone's floor defaults to this
110      * unless changed by some configured event.
111      *
112      * @return Default floor
113      */
getDefaultFloor() const114     inline const auto& getDefaultFloor() const
115     {
116         return _defaultFloor;
117     }
118 
119     /**
120      * @brief Get the increase delay(OPTIONAL)
121      *
122      * The increase delay is the amount of time(in seconds) increases
123      * to a target are delayed before being made. The default is 0, which
124      * results in immediate increase requests when any events result in
125      * a change to the target.
126      *
127      * It is recommend a value other than 0 is configured, but that inherently
128      * depends on the fan controller and configured increases.
129      *
130      * @return Increase delay(in seconds)
131      */
getIncDelay() const132     inline const auto& getIncDelay() const
133     {
134         return _incDelay;
135     }
136 
137     /**
138      * @brief Get the decrease interval
139      *
140      * Decreases happen on a set interval when no requests for an increase
141      * in fan targets exists. This is the interval(in seconds) at which the fans
142      * within the zone are decreased if events exist that result in a target
143      * decrease.
144      *
145      * @return Decrease interval(in seconds)
146      */
getDecInterval() const147     inline const auto& getDecInterval() const
148     {
149         return _decInterval;
150     }
151 
152     /**
153      * @brief Get the current target of the zone
154      *
155      * @return - The current target of the zone
156      */
getTarget() const157     inline const auto& getTarget() const
158     {
159         return _target;
160     }
161 
162     /**
163      * @brief Get the target increase delta
164      *
165      * @return - The current target increase delta
166      */
getIncDelta() const167     inline auto& getIncDelta() const
168     {
169         return _incDelta;
170     };
171 
172     /**
173      * @brief Get the target decrease delta
174      *
175      * @return - The current target decrease delta
176      */
getDecDelta() const177     inline auto& getDecDelta() const
178     {
179         return _decDelta;
180     };
181 
182     /**
183      * @brief Get the manager of the zone
184      *
185      * @return - The manager of the zone
186      */
getManager() const187     inline auto* getManager() const
188     {
189         return _manager;
190     }
191 
192     /**
193      * @brief Enable the zone
194      *
195      * Performs the necessary tasks to enable the zone such as restoring any
196      * dbus property states(if persisted), starting the decrement timer, etc...
197      */
198     void enable();
199 
200     /**
201      * @brief Add a fan object to the zone
202      *
203      * @param[in] fan - Unique pointer to a fan object that will be moved into
204      * the zone
205      *
206      * Adds a fan object to the list of fans that make up the zone by moving the
207      * fan object into the list.
208      */
209     void addFan(std::unique_ptr<Fan> fan);
210 
211     /**
212      * Sets all fans in the zone to the target given when the zone is active
213      *
214      * @param[in] target - Target for fans
215      */
216     void setTarget(uint64_t target);
217 
218     /**
219      * Add a target lock for the specified fan.
220      *
221      * @param[in] fname - Fan to request/add the target lock
222      * @param[in] target - Target to register
223      */
224     void lockFanTarget(const std::string& fname, uint64_t target);
225 
226     /**
227      * Remove target lock for specific fan.
228      *
229      * @param[in] fname - Fan to remove lock from
230      * @param[in] target- Target to de-register
231      */
232     void unlockFanTarget(const std::string& fname, uint64_t target);
233 
234     /**
235      * Sets and holds all fans in the zone to the target given or releases a
236      * target hold resulting in the fans being held at the highest remaining
237      * hold target if other hold targets had been requested. When no hold
238      * targets exist, the zone returns to being active.
239      *
240      * @param[in] ident - Unique identifier for a target hold
241      * @param[in] target - Target to hold fans at
242      * @param[in] hold - Whether to hold(true) or release(false) a target hold
243      */
244     void setTargetHold(const std::string& ident, uint64_t target, bool hold);
245 
246     /**
247      * @brief Set the floor to the given target and increase target to the floor
248      * when the target is below the floor value when floor changes are allowed.
249      *
250      * @param[in] target - Target to set the floor to
251      */
252     void setFloor(uint64_t target);
253 
254     /**
255      * Sets and holds the floor of the zone to the target given or releases a
256      * floor hold resulting in the fans being held at the highest remaining
257      * hold target if other floor hold targets had been requested. When no hold
258      * targets exist, the floor gets set to the default floor value.
259      *
260      * @param[in] ident - Unique identifier for a floor hold
261      * @param[in] target - Floor value
262      * @param[in] hold - Whether to hold(true) or release(false) a hold
263      */
264     void setFloorHold(const std::string& ident, uint64_t target, bool hold);
265 
266     /**
267      * @brief Says if the passed in identity has a floor hold
268      *
269      * @param ident - The identity key to check
270      * @return bool - If it has a floor hold or not
271      */
hasFloorHold(const std::string & ident) const272     inline bool hasFloorHold(const std::string& ident) const
273     {
274         return _floorHolds.contains(ident);
275     }
276 
277     /**
278      * @brief Set the default floor to the given value
279      *
280      * @param[in] value - Value to set the default floor to
281      */
setDefaultFloor(uint64_t value)282     inline void setDefaultFloor(uint64_t value)
283     {
284         _defaultFloor = value;
285     }
286 
287     /**
288      * @brief Sets the floor change allowed state
289      *
290      * @param[in] ident - An identifier that affects floor changes
291      * @param[in] isAllow - Allow state according to the identifier
292      */
setFloorChangeAllow(const std::string & ident,bool isAllow)293     inline void setFloorChangeAllow(const std::string& ident, bool isAllow)
294     {
295         _floorChange[ident] = isAllow;
296     }
297 
298     /**
299      * @brief Sets the decrease allowed state of a group
300      *
301      * @param[in] ident - An identifier that affects speed decreases
302      * @param[in] isAllow - Allow state according to the identifier
303      */
setDecreaseAllow(const std::string & ident,bool isAllow)304     inline void setDecreaseAllow(const std::string& ident, bool isAllow)
305     {
306         _decAllowed[ident] = isAllow;
307     }
308 
309     /**
310      * @brief Calculate the requested target from the given delta and increases
311      * the fans, not going above the ceiling.
312      *
313      * @param[in] targetDelta - The delta to increase the target by
314      */
315     void requestIncrease(uint64_t targetDelta);
316 
317     /**
318      * @brief Callback function for the increase timer that delays
319      * processing of requested target increases while fans are increasing
320      */
321     void incTimerExpired();
322 
323     /**
324      * @brief Calculate the lowest requested decrease target from the given
325      * delta within a decrease interval.
326      *
327      * @param[in] targetDelta - The delta to decrease the target by
328      */
329     void requestDecrease(uint64_t targetDelta);
330 
331     /**
332      * @brief Callback function for the decrease timer that processes any
333      * requested target decreases if allowed
334      */
335     void decTimerExpired();
336 
337     /**
338      * @brief Set the requested target base to be used as the target to base a
339      * new requested target from
340      *
341      * @param[in] targetBase - Base target value to use
342      */
setRequestTargetBase(uint64_t targetBase)343     inline void setRequestTargetBase(uint64_t targetBase)
344     {
345         _requestTargetBase = targetBase;
346     };
347 
348     /**
349      * @brief Set a property to be persisted
350      *
351      * @param[in] intf - Interface containing property
352      * @param[in] prop - Property to be persisted
353      */
354     void setPersisted(const std::string& intf, const std::string& prop);
355 
356     /**
357      * @brief Is the property persisted
358      *
359      * @param[in] intf - Interface containing property
360      * @param[in] prop - Property to check if persisted
361      *
362      * @return - True if property is to be persisted, false otherwise
363      */
364     bool isPersisted(const std::string& intf, const std::string& prop) const;
365 
366     /**
367      * @brief A handler function to set/update a property on a zone
368      * @details Sets or updates a zone's dbus property to the given value using
369      * the provided base dbus object's set property function
370      *
371      * @param[in] intf - Interface on zone object
372      * @param[in] prop - Property on interface
373      * @param[in] func - Zone dbus object's set property function pointer
374      * @param[in] value - Value to set property to
375      * @param[in] persist - Persist property value or not
376      *
377      * @return Lambda function
378      *     A lambda function to set/update the zone dbus object's property
379      */
380     template <typename T>
setProperty(const char * intf,const char * prop,T (DBusZone::* func)(T),T && value,bool persist)381     static auto setProperty(const char* intf, const char* prop,
382                             T (DBusZone::*func)(T), T&& value, bool persist)
383     {
384         return [=, value = std::forward<T>(value)](DBusZone& dbusZone,
385                                                    Zone& zone) {
386             (dbusZone.*func)(value);
387             if (persist)
388             {
389                 zone.setPersisted(intf, prop);
390             }
391         };
392     }
393 
394     /**
395      * @brief A handler function to set/update a zone's dbus property's persist
396      * state
397      * @details Sets or updates a zone's dbus property's persist state where the
398      * value of the property is to be left unchanged
399      *
400      * @param[in] intf - Interface on zone object
401      * @param[in] prop - Property on interface
402      * @param[in] persist - Persist property value or not
403      *
404      * @return Lambda function
405      *     A lambda function to set/update the zone's dbus object's property's
406      * persist state
407      */
setPropertyPersist(const char * intf,const char * prop,bool persist)408     static auto setPropertyPersist(const char* intf, const char* prop,
409                                    bool persist)
410     {
411         return [=](DBusZone&, Zone& zone) {
412             if (persist)
413             {
414                 zone.setPersisted(intf, prop);
415             }
416         };
417     }
418 
419     /**
420      * @brief Dump the attributes into JSON
421      *
422      * @return json - JSON object with the attributes
423      */
424     json dump() const;
425 
426   private:
427     /* The zone's associated dbus object */
428     std::unique_ptr<DBusZone> _dbusZone;
429 
430     /* The zone's manager */
431     Manager* _manager;
432 
433     /* The zone's poweron target value for fans */
434     uint64_t _poweronTarget;
435 
436     /* The zone's default ceiling value for fans */
437     uint64_t _defaultCeiling;
438 
439     /* The zone's default floor value for fans */
440     uint64_t _defaultFloor;
441 
442     /* Zone's increase delay(in seconds) (OPTIONAL) */
443     std::chrono::seconds _incDelay;
444 
445     /* Zone's decrease interval(in seconds) (OPTIONAL) */
446     std::chrono::seconds _decInterval;
447 
448     /* The floor target to not go below */
449     uint64_t _floor;
450 
451     /* Target for this zone */
452     uint64_t _target;
453 
454     /* Zone increase delta */
455     uint64_t _incDelta;
456 
457     /* Zone decrease delta */
458     uint64_t _decDelta;
459 
460     /* The ceiling target to not go above */
461     uint64_t _ceiling;
462 
463     /* Requested target base */
464     uint64_t _requestTargetBase;
465 
466     /* Map of whether floor changes are allowed by a string identifier */
467     std::map<std::string, bool> _floorChange;
468 
469     /* Map of controlling decreases allowed by a string identifer */
470     std::map<std::string, bool> _decAllowed;
471 
472     /* Map of interfaces to persisted properties the zone hosts*/
473     std::map<std::string, std::vector<std::string>> _propsPersisted;
474 
475     /* Automatic fan control active state */
476     bool _isActive;
477 
478     /* The target increase timer object */
479     Timer _incTimer;
480 
481     /* The target decrease timer object */
482     Timer _decTimer;
483 
484     /* Map of target holds by a string identifier */
485     std::unordered_map<std::string, uint64_t> _targetHolds;
486 
487     /* Map of floor holds by a string identifier */
488     std::unordered_map<std::string, uint64_t> _floorHolds;
489 
490     /* Interface to property mapping of their associated set property handler
491      * function */
492     static const std::map<
493         std::string,
494         std::map<std::string, std::function<std::function<void(
495                                   DBusZone&, Zone&)>(const json&, bool)>>>
496         _intfPropHandlers;
497 
498     /* List of fans included in this zone */
499     std::vector<std::unique_ptr<Fan>> _fans;
500 
501     /* List of configured interface set property functions */
502     std::vector<std::function<void(DBusZone&, Zone&)>> _propInitFunctions;
503 
504     /**
505      * @brief Parse and set the zone's poweron target value
506      *
507      * @param[in] jsonObj - JSON object for the zone
508      *
509      * Sets the poweron target value for the zone from the JSON configuration
510      * object
511      */
512     void setPowerOnTarget(const json& jsonObj);
513 
514     /**
515      * @brief Parse and set the interfaces served by the zone(OPTIONAL)
516      *
517      * @param[in] jsonObj - JSON object for the zone
518      *
519      * Constructs any zone interface handler functions for interfaces that the
520      * zone serves which contains the interface's property's value and
521      * persistency state (OPTIONAL). A property's "persist" state is defaulted
522      * to not be persisted when not given.
523      */
524     void setInterfaces(const json& jsonObj);
525 
526     /**
527      * @brief Get the request target base if defined, otherwise the the current
528      * target is returned
529      *
530      * @return - The request target base or current target
531      */
getRequestTargetBase() const532     inline auto getRequestTargetBase() const
533     {
534         return (_requestTargetBase != 0) ? _requestTargetBase : _target;
535     };
536 };
537 
538 /**
539  * Properties of interfaces supported by the zone configuration
540  */
541 namespace zone::property
542 {
543 
544 /**
545  * @brief "Supported" property on the "xyz.openbmc_project.Control.ThermalMode"
546  * interface parser. Also creates the handler function for the Zone dbus object
547  * to initialize the property according to what's parsed from the configuration.
548  *
549  * @param[in] jsonObj - JSON object for the "Supported" property
550  * @param[in] persist - Whether to persist the value or not
551  *
552  * @return Zone dbus object's set property function for the "Supported" property
553  */
554 std::function<void(DBusZone&, Zone&)> supported(const json& jsonObj,
555                                                 bool persist);
556 
557 /**
558  * @brief "Current" property on the "xyz.openbmc_project.Control.ThermalMode"
559  * interface parser. Also creates the handler function for the Zone dbus object
560  * to initialize the property according to what's parsed from the configuration.
561  *
562  * @param[in] jsonObj - JSON object for the "Current" property
563  * @param[in] persist - Whether to persist the value or not
564  *
565  * @return Zone dbus object's set property function for the "Current" property
566  */
567 std::function<void(DBusZone&, Zone&)> current(const json& jsonObj,
568                                               bool persist);
569 
570 } // namespace zone::property
571 
572 } // namespace phosphor::fan::control::json
573