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 = std::accumulate(
139                 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 = std::accumulate(
197                 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(
311             group.begin(), group.end(),
312             [&zone, &state, &factor, &speedDelta,
313              &netDelta](auto const& entry) {
314                 try
315                 {
316                     T value = zone.template getPropertyValue<T>(
317                         std::get<pathPos>(entry), std::get<intfPos>(entry),
318                         std::get<propPos>(entry));
319                     // TODO openbmc/phosphor-fan-presence#7 - Support possible
320                     // state types for comparison
321                     if (value >= state)
322                     {
323                         // Increase by at least a single delta(factor)
324                         // to attempt bringing under 'state'
325                         auto delta = std::max((value - state), factor);
326                         // Increase is the factor applied to the
327                         // difference times the given speed delta
328                         netDelta = std::max(netDelta,
329                                             static_cast<uint64_t>(
330                                                 (delta / factor) * speedDelta));
331                     }
332                 }
333                 catch (const std::out_of_range& oore)
334                 {
335                     // Property value not found, netDelta unchanged
336                 }
337             });
338         // Request speed change for target speed update
339         zone.requestSpeedIncrease(netDelta);
340     };
341 }
342 
343 /**
344  * @brief An action to set the speed decrease delta and request speed change
345  * @details Provides the ability to determine what the net decrease delta each
346  * zone's fan speeds should be updated by from their current target speed, and
347  * request that speed change occur on the next decrease interval.
348  *
349  * @param[in] state - State to compare the group's property value to
350  * @param[in] factor - Factor to apply to the calculated net delta
351  * @param[in] speedDelta - Speed delta of the group
352  *
353  * @return Lambda function
354  *     A lambda function that determines the net decrease delta and requests
355  * a new target speed with that decrease for the zone.
356  */
357 template <typename T>
358 auto set_net_decrease_speed(T&& state, T&& factor, uint64_t speedDelta)
359 {
360     return [speedDelta, factor = std::forward<T>(factor),
361             state = std::forward<T>(state)](auto& zone, auto& group) {
362         auto netDelta = zone.getDecSpeedDelta();
363         for (auto& entry : group)
364         {
365             try
366             {
367                 T value = zone.template getPropertyValue<T>(
368                     std::get<pathPos>(entry), std::get<intfPos>(entry),
369                     std::get<propPos>(entry));
370                 // TODO openbmc/phosphor-fan-presence#7 - Support possible
371                 // state types for comparison
372                 if (value < state)
373                 {
374                     if (netDelta == 0)
375                     {
376                         netDelta = ((state - value) / factor) * speedDelta;
377                     }
378                     else
379                     {
380                         // Decrease is the factor applied to the
381                         // difference times the given speed delta
382                         netDelta = std::min(
383                             netDelta,
384                             static_cast<uint64_t>(((state - value) / factor) *
385                                                   speedDelta));
386                     }
387                 }
388                 else
389                 {
390                     // No decrease allowed for this group
391                     netDelta = 0;
392                     break;
393                 }
394             }
395             catch (const std::out_of_range& oore)
396             {
397                 // Property value not found, netDelta unchanged
398             }
399         }
400         // Update group's decrease allowed state
401         zone.setDecreaseAllow(&group, !(netDelta == 0));
402         // Request speed decrease to occur on decrease interval
403         zone.requestSpeedDecrease(netDelta);
404     };
405 }
406 
407 /**
408  * @brief An action to use an alternate set of events
409  * @details Provides the ability to replace a default set of events with an
410  * alternate set of events based on all members of a group being at a specified
411  * state. When any member of the group no longer matches the provided state,
412  * the alternate set of events are replaced with the defaults.
413  *
414  * @param[in] state - State to compare the group's property value to
415  * @param[in] defEvents - The default set of events
416  * @param[in] altEvents - The alternate set of events
417  *
418  * @return Lambda function
419  *     A lambda function that checks all group members are at a specified state
420  * and replacing the default set of events with an alternate set of events.
421  */
422 template <typename T>
423 auto use_alternate_events_on_state(T&& state,
424                                    std::vector<SetSpeedEvent>&& defEvents,
425                                    std::vector<SetSpeedEvent>&& altEvents)
426 {
427     return [state = std::forward<T>(state), defEvents = std::move(defEvents),
428             altEvents = std::move(altEvents)](auto& zone, auto& group) {
429         // Compare all group entries to the state
430         auto useAlt = std::all_of(
431             group.begin(), group.end(), [&zone, &state](auto const& entry) {
432                 try
433                 {
434                     return zone.template getPropertyValue<T>(
435                                std::get<pathPos>(entry),
436                                std::get<intfPos>(entry),
437                                std::get<propPos>(entry)) == state;
438                 }
439                 catch (const std::out_of_range& oore)
440                 {
441                     // Default to property not equal when not found
442                     return false;
443                 }
444             });
445 
446         const std::vector<SetSpeedEvent>* rmEvents = &altEvents;
447         const std::vector<SetSpeedEvent>* initEvents = &defEvents;
448 
449         if (useAlt)
450         {
451             rmEvents = &defEvents;
452             initEvents = &altEvents;
453         }
454 
455         // Remove events
456         std::for_each(rmEvents->begin(), rmEvents->end(),
457                       [&zone](auto const& entry) { zone.removeEvent(entry); });
458         // Init events
459         std::for_each(initEvents->begin(), initEvents->end(),
460                       [&zone](auto const& entry) { zone.initEvent(entry); });
461     };
462 }
463 
464 /**
465  * @brief An action to set the floor speed on a zone
466  * @details Using sensor group values that are within a defined range, the
467  * floor speed is selected from the first map key entry that the median
468  * sensor value is less than where 3 or more sensor group values are valid.
469  * In the case where less than 3 sensor values are valid, use the highest
470  * sensor group value and default the floor speed when 0 sensor group values
471  * are valid.
472  *
473  * @param[in] lowerBound - Lowest allowed sensor value to be valid
474  * @param[in] upperBound - Highest allowed sensor value to be valid
475  * @param[in] valueToSpeed - Ordered map of sensor value-to-speed
476  *
477  * @return Action lambda function
478  *     An Action function to set the zone's floor speed from a resulting group
479  * of valid sensor values based on their highest value or median.
480  */
481 template <typename T>
482 Action set_floor_from_median_sensor_value(T&& lowerBound, T&& upperBound,
483                                           std::map<T, uint64_t>&& valueToSpeed)
484 {
485     return [lowerBound = std::forward<T>(lowerBound),
486             upperBound = std::forward<T>(upperBound),
487             valueToSpeed = std::move(valueToSpeed)](control::Zone& zone,
488                                                     const Group& group) {
489         auto speed = zone.getDefFloor();
490         if (group.size() != 0)
491         {
492             std::vector<T> validValues;
493             for (auto const& member : group)
494             {
495                 try
496                 {
497                     auto value = zone.template getPropertyValue<T>(
498                         std::get<pathPos>(member), std::get<intfPos>(member),
499                         std::get<propPos>(member));
500                     if (value == std::clamp(value, lowerBound, upperBound))
501                     {
502                         // Sensor value is valid
503                         validValues.emplace_back(value);
504                     }
505                 }
506                 catch (const std::out_of_range& oore)
507                 {
508                     continue;
509                 }
510             }
511 
512             if (!validValues.empty())
513             {
514                 auto median = validValues.front();
515                 // Get the determined median value
516                 if (validValues.size() == 2)
517                 {
518                     // For 2 values, use the highest instead of the average
519                     // for a thermally safe floor
520                     median = *std::max_element(validValues.begin(),
521                                                validValues.end());
522                 }
523                 else if (validValues.size() > 2)
524                 {
525                     median = utility::getMedian(validValues);
526                 }
527 
528                 // Use determined median sensor value to find floor speed
529                 auto it = std::find_if(valueToSpeed.begin(), valueToSpeed.end(),
530                                        [&median](auto const& entry) {
531                                            return median < entry.first;
532                                        });
533                 if (it != std::end(valueToSpeed))
534                 {
535                     speed = (*it).second;
536                 }
537             }
538         }
539         zone.setFloor(speed);
540     };
541 }
542 
543 /**
544  * @brief An action to update the default floor speed
545  * @details Provides the ability to update the default fan floor speed when
546  * all of the group members property values match the value given
547  *
548  * @param[in] state - State to compare the group's property value to
549  * @param[in] speed - Speed to set the default fan floor to
550  *
551  * @return Lambda function
552  *     A lambda function that checks all group members are at a specified state
553  * and updates the default fan floor speed.
554  */
555 template <typename T>
556 auto update_default_floor(T&& state, uint64_t speed)
557 {
558     return [speed, state = std::forward<T>(state)](auto& zone, auto& group) {
559         auto updateDefFloor = std::all_of(
560             group.begin(), group.end(), [&zone, &state](auto const& entry) {
561                 try
562                 {
563                     return zone.template getPropertyValue<T>(
564                                std::get<pathPos>(entry),
565                                std::get<intfPos>(entry),
566                                std::get<propPos>(entry)) == state;
567                 }
568                 catch (const std::out_of_range& oore)
569                 {
570                     // Default to property not equal when not found
571                     return false;
572                 }
573             });
574 
575         if (!updateDefFloor)
576         {
577             // Do not update the default floor
578             return;
579         }
580 
581         // Set/update the default floor of the zone
582         zone.setDefFloor(speed);
583     };
584 }
585 
586 /**
587  * @brief An action to use a set of events
588  * @details Provides the ability to use a set of events when all members of
589  * a group are at a specified state. When any member of the group no longer
590  * matches the provided state the set of events are removed.
591  *
592  * @param[in] state - State to compare the group's property value to
593  * @param[in] events - The set of events
594  *
595  * @return Lambda function
596  *     A lambda function that checks all group members are at a specified state
597  * and initializes the set of events, otherwise removes them.
598  */
599 template <typename T>
600 auto use_events_on_state(T&& state, std::vector<SetSpeedEvent>&& events)
601 {
602     return [state = std::forward<T>(state),
603             events = std::move(events)](auto& zone, auto& group) {
604         // Compare all group entries to the state
605         auto useEvents = std::all_of(
606             group.begin(), group.end(), [&zone, &state](auto const& entry) {
607                 try
608                 {
609                     return zone.template getPropertyValue<T>(
610                                std::get<pathPos>(entry),
611                                std::get<intfPos>(entry),
612                                std::get<propPos>(entry)) == state;
613                 }
614                 catch (const std::out_of_range& oore)
615                 {
616                     // Default to property not equal when not found
617                     return false;
618                 }
619             });
620 
621         if (useEvents)
622         {
623             // Init events
624             std::for_each(
625                 events.begin(), events.end(),
626                 [&zone](auto const& entry) { zone.initEvent(entry); });
627         }
628         else
629         {
630             // Remove events
631             std::for_each(
632                 events.begin(), events.end(),
633                 [&zone](auto const& entry) { zone.removeEvent(entry); });
634         }
635     };
636 }
637 
638 } // namespace action
639 } // namespace control
640 } // namespace fan
641 } // namespace phosphor
642