1 #pragma once
2 
3 #include "types.hpp"
4 #include "utility.hpp"
5 #include "zone.hpp"
6 
7 #include <algorithm>
8 #include <numeric>
9 
10 namespace phosphor
11 {
12 namespace fan
13 {
14 namespace control
15 {
16 namespace action
17 {
18 
19 /**
20  * @brief An action that wraps a list of actions with a timer
21  * @details Sets up a list of actions to be invoked when the defined timer
22  * expires (or for each expiration of a repeating timer).
23  *
24  * @param[in] tConf - Timer configuration parameters
25  * @param[in] action - List of actions to be called when the timer expires
26  *
27  * @return Action lambda function
28  *     An Action function that creates a timer
29  */
30 Action call_actions_based_on_timer(TimerConf&& tConf,
31                                    std::vector<Action>&& actions);
32 
33 /**
34  * @brief An action that sets the floor to the default fan floor speed
35  * @details Sets the fan floor to the defined default fan floor speed when a
36  * service associated to the given group has terminated. Once all services
37  * are functional and providing the sensors again, the fan floor is allowed
38  * to be set normally.
39  *
40  * @param[in] zone - Zone containing fans
41  * @param[in] group - Group of sensors to determine services' states
42  */
43 void default_floor_on_missing_owner(Zone& zone, const Group& group);
44 
45 /**
46  * @brief An action to set a speed when a service owner is missing
47  * @details Sets the fans to the given speed when any service owner associated
48  * to the group is missing. Once all services are functional and providing
49  * the event data again, active fan speed changes are allowed.
50  *
51  * @param[in] speed - Speed to set the zone to
52  *
53  * @return Action lambda function
54  *     An Action function that sets the zone to the given speed if any service
55  *     owners are missing.
56  */
57 Action set_speed_on_missing_owner(uint64_t speed);
58 
59 /**
60  * @brief An action to set the request speed base
61  * @details A new target speed is determined using a speed delta being added
62  * or subtracted, for increases or decrease respectively, from a base speed.
63  * This base speed defaults to be the current target speed or is set to a
64  * different base speed(i.e. the fans' tach feedback speed) to request a new
65  * target from.
66  *
67  * @param[in] zone - Zone containing fans
68  * @param[in] group - Group of sensors to determine base from
69  */
70 void set_request_speed_base_with_max(Zone& zone, const Group& group);
71 
72 /**
73  * @brief An action to set the speed on a zone
74  * @details The zone is held at the given speed when a defined number of
75  * properties in the group are set to the given state
76  *
77  * @param[in] count - Number of properties
78  * @param[in] state - Value the property(s) needed to be set at
79  * @param[in] speed - Speed to set the zone to
80  *
81  * @return Lambda function
82  *     A lambda function to set the zone speed when the number of properties
83  *     within the group are at a certain value
84  */
85 template <typename T>
86 auto count_state_before_speed(size_t count, T&& state, uint64_t speed)
87 {
88     return [count, speed, state = std::forward<T>(state)](auto& zone,
89                                                           auto& group) {
90         size_t numAtState = 0;
91         for (auto& entry : group)
92         {
93             try
94             {
95                 if (zone.template getPropertyValue<T>(
96                         std::get<pathPos>(entry), std::get<intfPos>(entry),
97                         std::get<propPos>(entry)) == state)
98                 {
99                     numAtState++;
100                 }
101             }
102             catch (const std::out_of_range& oore)
103             {
104                 // Default to property not equal when not found
105             }
106             if (numAtState >= count)
107             {
108                 zone.setSpeed(speed);
109                 break;
110             }
111         }
112         // Update group's fan control active allowed based on action results
113         zone.setActiveAllow(&group, !(numAtState >= count));
114     };
115 }
116 
117 /**
118  * @brief An action to set the floor speed on a zone
119  * @details Based on the average of the defined sensor group values, the floor
120  * speed is selected from the first map key entry that the average sensor value
121  * is less than.
122  *
123  * @param[in] val_to_speed - Ordered map of sensor value-to-speed
124  *
125  * @return Action lambda function
126  *     An Action function to set the zone's floor speed when the average of
127  *     property values within the group is below the lowest sensor value given
128  */
129 template <typename T>
130 Action set_floor_from_average_sensor_value(std::map<T, uint64_t>&& val_to_speed)
131 {
132     return [val_to_speed = std::move(val_to_speed)](control::Zone& zone,
133                                                     const Group& group) {
134         auto speed = zone.getDefFloor();
135         if (group.size() != 0)
136         {
137             auto count = 0;
138             auto sumValue =
139                 std::accumulate(group.begin(), group.end(), 0,
140                                 [&zone, &count](T sum, auto const& entry) {
141                 try
142                 {
143                     return sum + zone.template getPropertyValue<T>(
144                                      std::get<pathPos>(entry),
145                                      std::get<intfPos>(entry),
146                                      std::get<propPos>(entry));
147                 }
148                 catch (const std::out_of_range& oore)
149                 {
150                     count++;
151                     return sum;
152                 }
153                 });
154             if ((group.size() - count) > 0)
155             {
156                 auto groupSize = static_cast<int64_t>(group.size());
157                 auto avgValue = sumValue / (groupSize - count);
158                 auto it = std::find_if(val_to_speed.begin(), val_to_speed.end(),
159                                        [&avgValue](auto const& entry) {
160                     return avgValue < entry.first;
161                 });
162                 if (it != std::end(val_to_speed))
163                 {
164                     speed = (*it).second;
165                 }
166             }
167         }
168         zone.setFloor(speed);
169     };
170 }
171 
172 /**
173  * @brief An action to set the ceiling speed on a zone
174  * @details Based on the average of the defined sensor group values, the
175  * ceiling speed is selected from the map key transition point that the average
176  * sensor value falls within depending on the key values direction from what
177  * was previously read.
178  *
179  * @param[in] val_to_speed - Ordered map of sensor value-to-speed transitions
180  *
181  * @return Action lambda function
182  *     An Action function to set the zone's ceiling speed when the average of
183  *     property values within the group is above(increasing) or
184  *     below(decreasing) the key transition point
185  */
186 template <typename T>
187 Action
188     set_ceiling_from_average_sensor_value(std::map<T, uint64_t>&& val_to_speed)
189 {
190     return [val_to_speed = std::move(val_to_speed)](Zone& zone,
191                                                     const Group& group) {
192         auto speed = zone.getCeiling();
193         if (group.size() != 0)
194         {
195             auto count = 0;
196             auto sumValue =
197                 std::accumulate(group.begin(), group.end(), 0,
198                                 [&zone, &count](T sum, auto const& entry) {
199                 try
200                 {
201                     return sum + zone.template getPropertyValue<T>(
202                                      std::get<pathPos>(entry),
203                                      std::get<intfPos>(entry),
204                                      std::get<propPos>(entry));
205                 }
206                 catch (const std::out_of_range& oore)
207                 {
208                     count++;
209                     return sum;
210                 }
211                 });
212             if ((group.size() - count) > 0)
213             {
214                 auto groupSize = static_cast<int64_t>(group.size());
215                 auto avgValue = sumValue / (groupSize - count);
216                 auto prevValue = zone.swapCeilingKeyValue(avgValue);
217                 if (avgValue != prevValue)
218                 {     // Only check if previous and new values differ
219                     if (avgValue < prevValue)
220                     { // Value is decreasing from previous
221                         for (auto it = val_to_speed.rbegin();
222                              it != val_to_speed.rend(); ++it)
223                         {
224                             if (it == val_to_speed.rbegin() &&
225                                 avgValue >= it->first)
226                             {
227                                 // Value is at/above last map key, set
228                                 // ceiling speed to the last map key's value
229                                 speed = it->second;
230                                 break;
231                             }
232                             else if (std::next(it, 1) == val_to_speed.rend() &&
233                                      avgValue <= it->first)
234                             {
235                                 // Value is at/below first map key, set
236                                 // ceiling speed to the first map key's value
237                                 speed = it->second;
238                                 break;
239                             }
240                             if (avgValue < it->first && it->first <= prevValue)
241                             {
242                                 // Value decreased & transitioned across
243                                 // a map key, update ceiling speed to this
244                                 // map key's value when new value is below
245                                 // map's key and the key is at/below the
246                                 // previous value
247                                 speed = it->second;
248                             }
249                         }
250                     }
251                     else
252                     { // Value is increasing from previous
253                         for (auto it = val_to_speed.begin();
254                              it != val_to_speed.end(); ++it)
255                         {
256                             if (it == val_to_speed.begin() &&
257                                 avgValue <= it->first)
258                             {
259                                 // Value is at/below first map key, set
260                                 // ceiling speed to the first map key's value
261                                 speed = it->second;
262                                 break;
263                             }
264                             else if (std::next(it, 1) == val_to_speed.end() &&
265                                      avgValue >= it->first)
266                             {
267                                 // Value is at/above last map key, set
268                                 // ceiling speed to the last map key's value
269                                 speed = it->second;
270                                 break;
271                             }
272                             if (avgValue > it->first && it->first >= prevValue)
273                             {
274                                 // Value increased & transitioned across
275                                 // a map key, update ceiling speed to this
276                                 // map key's value when new value is above
277                                 // map's key and the key is at/above the
278                                 // previous value
279                                 speed = it->second;
280                             }
281                         }
282                     }
283                 }
284             }
285         }
286         zone.setCeiling(speed);
287     };
288 }
289 
290 /**
291  * @brief An action to set the speed increase delta and request speed change
292  * @details Provides the ability to determine what the net increase delta the
293  * zone's fan speeds should be updated by from their current target speed and
294  * request that new target speed.
295  *
296  * @param[in] state - State to compare the group's property value to
297  * @param[in] factor - Factor to apply to the calculated net delta
298  * @param[in] speedDelta - Speed delta of the group
299  *
300  * @return Lambda function
301  *     A lambda function that determines the net increase delta and requests
302  * a new target speed with that increase for the zone.
303  */
304 template <typename T>
305 auto set_net_increase_speed(T&& state, T&& factor, uint64_t speedDelta)
306 {
307     return [speedDelta, factor = std::forward<T>(factor),
308             state = std::forward<T>(state)](auto& zone, auto& group) {
309         auto netDelta = zone.getIncSpeedDelta();
310         std::for_each(group.begin(), group.end(),
311                       [&zone, &state, &factor, &speedDelta,
312                        &netDelta](auto const& entry) {
313             try
314             {
315                 T value = zone.template getPropertyValue<T>(
316                     std::get<pathPos>(entry), std::get<intfPos>(entry),
317                     std::get<propPos>(entry));
318                 // TODO openbmc/phosphor-fan-presence#7 - Support possible
319                 // state types for comparison
320                 if (value >= state)
321                 {
322                     // Increase by at least a single delta(factor)
323                     // to attempt bringing under 'state'
324                     auto delta = std::max((value - state), factor);
325                     // Increase is the factor applied to the
326                     // difference times the given speed delta
327                     netDelta = std::max(
328                         netDelta,
329                         static_cast<uint64_t>((delta / factor) * speedDelta));
330                 }
331             }
332             catch (const std::out_of_range& oore)
333             {
334                 // Property value not found, netDelta unchanged
335             }
336         });
337         // Request speed change for target speed update
338         zone.requestSpeedIncrease(netDelta);
339     };
340 }
341 
342 /**
343  * @brief An action to set the speed decrease delta and request speed change
344  * @details Provides the ability to determine what the net decrease delta each
345  * zone's fan speeds should be updated by from their current target speed, and
346  * request that speed change occur on the next decrease interval.
347  *
348  * @param[in] state - State to compare the group's property value to
349  * @param[in] factor - Factor to apply to the calculated net delta
350  * @param[in] speedDelta - Speed delta of the group
351  *
352  * @return Lambda function
353  *     A lambda function that determines the net decrease delta and requests
354  * a new target speed with that decrease for the zone.
355  */
356 template <typename T>
357 auto set_net_decrease_speed(T&& state, T&& factor, uint64_t speedDelta)
358 {
359     return [speedDelta, factor = std::forward<T>(factor),
360             state = std::forward<T>(state)](auto& zone, auto& group) {
361         auto netDelta = zone.getDecSpeedDelta();
362         for (auto& entry : group)
363         {
364             try
365             {
366                 T value = zone.template getPropertyValue<T>(
367                     std::get<pathPos>(entry), std::get<intfPos>(entry),
368                     std::get<propPos>(entry));
369                 // TODO openbmc/phosphor-fan-presence#7 - Support possible
370                 // state types for comparison
371                 if (value < state)
372                 {
373                     if (netDelta == 0)
374                     {
375                         netDelta = ((state - value) / factor) * speedDelta;
376                     }
377                     else
378                     {
379                         // Decrease is the factor applied to the
380                         // difference times the given speed delta
381                         netDelta = std::min(
382                             netDelta,
383                             static_cast<uint64_t>(((state - value) / factor) *
384                                                   speedDelta));
385                     }
386                 }
387                 else
388                 {
389                     // No decrease allowed for this group
390                     netDelta = 0;
391                     break;
392                 }
393             }
394             catch (const std::out_of_range& oore)
395             {
396                 // Property value not found, netDelta unchanged
397             }
398         }
399         // Update group's decrease allowed state
400         zone.setDecreaseAllow(&group, !(netDelta == 0));
401         // Request speed decrease to occur on decrease interval
402         zone.requestSpeedDecrease(netDelta);
403     };
404 }
405 
406 /**
407  * @brief An action to use an alternate set of events
408  * @details Provides the ability to replace a default set of events with an
409  * alternate set of events based on all members of a group being at a specified
410  * state. When any member of the group no longer matches the provided state,
411  * the alternate set of events are replaced with the defaults.
412  *
413  * @param[in] state - State to compare the group's property value to
414  * @param[in] defEvents - The default set of events
415  * @param[in] altEvents - The alternate set of events
416  *
417  * @return Lambda function
418  *     A lambda function that checks all group members are at a specified state
419  * and replacing the default set of events with an alternate set of events.
420  */
421 template <typename T>
422 auto use_alternate_events_on_state(T&& state,
423                                    std::vector<SetSpeedEvent>&& defEvents,
424                                    std::vector<SetSpeedEvent>&& altEvents)
425 {
426     return [state = std::forward<T>(state), defEvents = std::move(defEvents),
427             altEvents = std::move(altEvents)](auto& zone, auto& group) {
428         // Compare all group entries to the state
429         auto useAlt = std::all_of(group.begin(), group.end(),
430                                   [&zone, &state](auto const& entry) {
431             try
432             {
433                 return zone.template getPropertyValue<T>(
434                            std::get<pathPos>(entry), std::get<intfPos>(entry),
435                            std::get<propPos>(entry)) == state;
436             }
437             catch (const std::out_of_range& oore)
438             {
439                 // Default to property not equal when not found
440                 return false;
441             }
442         });
443 
444         const std::vector<SetSpeedEvent>* rmEvents = &altEvents;
445         const std::vector<SetSpeedEvent>* initEvents = &defEvents;
446 
447         if (useAlt)
448         {
449             rmEvents = &defEvents;
450             initEvents = &altEvents;
451         }
452 
453         // Remove events
454         std::for_each(rmEvents->begin(), rmEvents->end(),
455                       [&zone](auto const& entry) { zone.removeEvent(entry); });
456         // Init events
457         std::for_each(initEvents->begin(), initEvents->end(),
458                       [&zone](auto const& entry) { zone.initEvent(entry); });
459     };
460 }
461 
462 /**
463  * @brief An action to set the floor speed on a zone
464  * @details Using sensor group values that are within a defined range, the
465  * floor speed is selected from the first map key entry that the median
466  * sensor value is less than where 3 or more sensor group values are valid.
467  * In the case where less than 3 sensor values are valid, use the highest
468  * sensor group value and default the floor speed when 0 sensor group values
469  * are valid.
470  *
471  * @param[in] lowerBound - Lowest allowed sensor value to be valid
472  * @param[in] upperBound - Highest allowed sensor value to be valid
473  * @param[in] valueToSpeed - Ordered map of sensor value-to-speed
474  *
475  * @return Action lambda function
476  *     An Action function to set the zone's floor speed from a resulting group
477  * of valid sensor values based on their highest value or median.
478  */
479 template <typename T>
480 Action set_floor_from_median_sensor_value(T&& lowerBound, T&& upperBound,
481                                           std::map<T, uint64_t>&& valueToSpeed)
482 {
483     return [lowerBound = std::forward<T>(lowerBound),
484             upperBound = std::forward<T>(upperBound),
485             valueToSpeed = std::move(valueToSpeed)](control::Zone& zone,
486                                                     const Group& group) {
487         auto speed = zone.getDefFloor();
488         if (group.size() != 0)
489         {
490             std::vector<T> validValues;
491             for (auto const& member : group)
492             {
493                 try
494                 {
495                     auto value = zone.template getPropertyValue<T>(
496                         std::get<pathPos>(member), std::get<intfPos>(member),
497                         std::get<propPos>(member));
498                     if (value == std::clamp(value, lowerBound, upperBound))
499                     {
500                         // Sensor value is valid
501                         validValues.emplace_back(value);
502                     }
503                 }
504                 catch (const std::out_of_range& oore)
505                 {
506                     continue;
507                 }
508             }
509 
510             if (!validValues.empty())
511             {
512                 auto median = validValues.front();
513                 // Get the determined median value
514                 if (validValues.size() == 2)
515                 {
516                     // For 2 values, use the highest instead of the average
517                     // for a thermally safe floor
518                     median = *std::max_element(validValues.begin(),
519                                                validValues.end());
520                 }
521                 else if (validValues.size() > 2)
522                 {
523                     median = utility::getMedian(validValues);
524                 }
525 
526                 // Use determined median sensor value to find floor speed
527                 auto it = std::find_if(valueToSpeed.begin(), valueToSpeed.end(),
528                                        [&median](auto const& entry) {
529                     return median < entry.first;
530                 });
531                 if (it != std::end(valueToSpeed))
532                 {
533                     speed = (*it).second;
534                 }
535             }
536         }
537         zone.setFloor(speed);
538     };
539 }
540 
541 /**
542  * @brief An action to update the default floor speed
543  * @details Provides the ability to update the default fan floor speed when
544  * all of the group members property values match the value given
545  *
546  * @param[in] state - State to compare the group's property value to
547  * @param[in] speed - Speed to set the default fan floor to
548  *
549  * @return Lambda function
550  *     A lambda function that checks all group members are at a specified state
551  * and updates the default fan floor speed.
552  */
553 template <typename T>
554 auto update_default_floor(T&& state, uint64_t speed)
555 {
556     return [speed, state = std::forward<T>(state)](auto& zone, auto& group) {
557         auto updateDefFloor = std::all_of(group.begin(), group.end(),
558                                           [&zone, &state](auto const& entry) {
559             try
560             {
561                 return zone.template getPropertyValue<T>(
562                            std::get<pathPos>(entry), std::get<intfPos>(entry),
563                            std::get<propPos>(entry)) == state;
564             }
565             catch (const std::out_of_range& oore)
566             {
567                 // Default to property not equal when not found
568                 return false;
569             }
570         });
571 
572         if (!updateDefFloor)
573         {
574             // Do not update the default floor
575             return;
576         }
577 
578         // Set/update the default floor of the zone
579         zone.setDefFloor(speed);
580     };
581 }
582 
583 /**
584  * @brief An action to use a set of events
585  * @details Provides the ability to use a set of events when all members of
586  * a group are at a specified state. When any member of the group no longer
587  * matches the provided state the set of events are removed.
588  *
589  * @param[in] state - State to compare the group's property value to
590  * @param[in] events - The set of events
591  *
592  * @return Lambda function
593  *     A lambda function that checks all group members are at a specified state
594  * and initializes the set of events, otherwise removes them.
595  */
596 template <typename T>
597 auto use_events_on_state(T&& state, std::vector<SetSpeedEvent>&& events)
598 {
599     return [state = std::forward<T>(state),
600             events = std::move(events)](auto& zone, auto& group) {
601         // Compare all group entries to the state
602         auto useEvents = std::all_of(group.begin(), group.end(),
603                                      [&zone, &state](auto const& entry) {
604             try
605             {
606                 return zone.template getPropertyValue<T>(
607                            std::get<pathPos>(entry), std::get<intfPos>(entry),
608                            std::get<propPos>(entry)) == state;
609             }
610             catch (const std::out_of_range& oore)
611             {
612                 // Default to property not equal when not found
613                 return false;
614             }
615         });
616 
617         if (useEvents)
618         {
619             // Init events
620             std::for_each(events.begin(), events.end(),
621                           [&zone](auto const& entry) {
622                 zone.initEvent(entry);
623             });
624         }
625         else
626         {
627             // Remove events
628             std::for_each(events.begin(), events.end(),
629                           [&zone](auto const& entry) {
630                 zone.removeEvent(entry);
631             });
632         }
633     };
634 }
635 
636 } // namespace action
637 } // namespace control
638 } // namespace fan
639 } // namespace phosphor
640