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