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