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