1 /** 2 * Copyright © 2020 IBM Corporation 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 #include "zone.hpp" 17 18 #include "../utils/flight_recorder.hpp" 19 #include "dbus_zone.hpp" 20 #include "fan.hpp" 21 #include "sdbusplus.hpp" 22 23 #include <nlohmann/json.hpp> 24 #include <phosphor-logging/log.hpp> 25 #include <sdeventplus/event.hpp> 26 27 #include <algorithm> 28 #include <chrono> 29 #include <iterator> 30 #include <map> 31 #include <memory> 32 #include <numeric> 33 #include <utility> 34 #include <vector> 35 36 namespace phosphor::fan::control::json 37 { 38 39 using json = nlohmann::json; 40 using namespace phosphor::logging; 41 42 const std::map< 43 std::string, 44 std::map<std::string, std::function<std::function<void(DBusZone&, Zone&)>( 45 const json&, bool)>>> 46 Zone::_intfPropHandlers = { 47 {DBusZone::thermalModeIntf, 48 {{DBusZone::supportedProp, zone::property::supported}, 49 {DBusZone::currentProp, zone::property::current}}}}; 50 51 Zone::Zone(const json& jsonObj, const sdeventplus::Event& event, Manager* mgr) : 52 ConfigBase(jsonObj), _dbusZone{}, _manager(mgr), _defaultFloor(0), 53 _incDelay(0), _decInterval(0), _floor(0), _target(0), _incDelta(0), 54 _decDelta(0), _requestTargetBase(0), _isActive(true), 55 _incTimer(event, std::bind(&Zone::incTimerExpired, this)), 56 _decTimer(event, std::bind(&Zone::decTimerExpired, this)) 57 { 58 // Increase delay is optional, defaults to 0 59 if (jsonObj.contains("increase_delay")) 60 { 61 _incDelay = 62 std::chrono::seconds(jsonObj["increase_delay"].get<uint64_t>()); 63 } 64 65 // Poweron target is required 66 setPowerOnTarget(jsonObj); 67 68 // Default ceiling is optional, defaults to poweron target 69 _defaultCeiling = _poweronTarget; 70 if (jsonObj.contains("default_ceiling")) 71 { 72 _defaultCeiling = jsonObj["default_ceiling"].get<uint64_t>(); 73 } 74 // Start with the current ceiling set as the default ceiling 75 _ceiling = _defaultCeiling; 76 77 // Default floor is optional, defaults to 0 78 if (jsonObj.contains("default_floor")) 79 { 80 _defaultFloor = jsonObj["default_floor"].get<uint64_t>(); 81 // Start with the current floor set as the default 82 _floor = _defaultFloor; 83 } 84 85 // Decrease interval is optional, defaults to 0 86 // A decrease interval of 0sec disables the decrease timer 87 if (jsonObj.contains("decrease_interval")) 88 { 89 _decInterval = 90 std::chrono::seconds(jsonObj["decrease_interval"].get<uint64_t>()); 91 } 92 93 // Setting properties on interfaces to be served are optional 94 if (jsonObj.contains("interfaces")) 95 { 96 setInterfaces(jsonObj); 97 } 98 } 99 100 void Zone::enable() 101 { 102 // Create thermal control dbus object 103 _dbusZone = std::make_unique<DBusZone>(*this); 104 105 // Init all configured dbus interfaces' property states 106 for (const auto& func : _propInitFunctions) 107 { 108 // Only call non-null init property functions 109 if (func) 110 { 111 func(*_dbusZone, *this); 112 } 113 } 114 115 // TODO - Restore any persisted properties in init function 116 // Restore thermal control current mode state, if exists 117 _dbusZone->restoreCurrentMode(); 118 119 // Emit object added for this zone's associated dbus object 120 _dbusZone->emit_object_added(); 121 122 // A decrease interval of 0sec disables the decrease timer 123 if (_decInterval != std::chrono::seconds::zero()) 124 { 125 // Start timer for fan target decreases 126 _decTimer.restart(_decInterval); 127 } 128 } 129 130 void Zone::addFan(std::unique_ptr<Fan> fan) 131 { 132 _fans.emplace_back(std::move(fan)); 133 } 134 135 void Zone::setTarget(uint64_t target) 136 { 137 if (_isActive) 138 { 139 _target = target; 140 for (auto& fan : _fans) 141 { 142 fan->setTarget(_target); 143 } 144 } 145 } 146 147 void Zone::setTargetHold(const std::string& ident, uint64_t target, bool hold) 148 { 149 using namespace std::string_literals; 150 151 if (!hold) 152 { 153 size_t removed = _targetHolds.erase(ident); 154 if (removed) 155 { 156 FlightRecorder::instance().log( 157 "zone-target"s + getName(), 158 fmt::format("{} is removing target hold", ident)); 159 } 160 } 161 else 162 { 163 if (!((_targetHolds.find(ident) != _targetHolds.end()) && 164 (_targetHolds[ident] == target))) 165 { 166 FlightRecorder::instance().log( 167 "zone-target"s + getName(), 168 fmt::format("{} is setting target hold to {}", ident, target)); 169 } 170 _targetHolds[ident] = target; 171 _isActive = false; 172 } 173 174 auto itHoldMax = std::max_element(_targetHolds.begin(), _targetHolds.end(), 175 [](const auto& aHold, const auto& bHold) { 176 return aHold.second < bHold.second; 177 }); 178 if (itHoldMax == _targetHolds.end()) 179 { 180 _isActive = true; 181 } 182 else 183 { 184 if (_target != itHoldMax->second) 185 { 186 FlightRecorder::instance().log( 187 "zone-target"s + getName(), 188 fmt::format("Settings fans to target hold of {}", 189 itHoldMax->second)); 190 } 191 192 _target = itHoldMax->second; 193 for (auto& fan : _fans) 194 { 195 fan->setTarget(_target); 196 } 197 } 198 } 199 200 void Zone::setFloorHold(const std::string& ident, uint64_t target, bool hold) 201 { 202 using namespace std::string_literals; 203 204 if (!hold) 205 { 206 size_t removed = _floorHolds.erase(ident); 207 if (removed) 208 { 209 FlightRecorder::instance().log( 210 "zone-floor"s + getName(), 211 fmt::format("{} is removing floor hold", ident)); 212 } 213 } 214 else 215 { 216 if (!((_floorHolds.find(ident) != _floorHolds.end()) && 217 (_floorHolds[ident] == target))) 218 { 219 FlightRecorder::instance().log( 220 "zone-floor"s + getName(), 221 fmt::format("{} is setting floor hold to {}", ident, target)); 222 } 223 _floorHolds[ident] = target; 224 } 225 226 if (!std::all_of(_floorChange.begin(), _floorChange.end(), 227 [](const auto& entry) { return entry.second; })) 228 { 229 return; 230 } 231 232 auto itHoldMax = std::max_element(_floorHolds.begin(), _floorHolds.end(), 233 [](const auto& aHold, const auto& bHold) { 234 return aHold.second < bHold.second; 235 }); 236 if (itHoldMax == _floorHolds.end()) 237 { 238 if (_floor != _defaultFloor) 239 { 240 FlightRecorder::instance().log( 241 "zone-floor"s + getName(), 242 fmt::format("No set floor exists, using default floor", 243 _defaultFloor)); 244 } 245 _floor = _defaultFloor; 246 } 247 else 248 { 249 if (_floor != itHoldMax->second) 250 { 251 FlightRecorder::instance().log( 252 "zone-floor"s + getName(), 253 fmt::format("Setting new floor to {}", itHoldMax->second)); 254 } 255 _floor = itHoldMax->second; 256 } 257 258 // Floor above target, update target to floor 259 if (_target < _floor) 260 { 261 requestIncrease(_floor - _target); 262 } 263 } 264 265 void Zone::setFloor(uint64_t target) 266 { 267 // Check all entries are set to allow floor to be set 268 auto pred = [](const auto& entry) { return entry.second; }; 269 if (std::all_of(_floorChange.begin(), _floorChange.end(), pred)) 270 { 271 _floor = target; 272 // Floor above target, update target to floor 273 if (_target < _floor) 274 { 275 requestIncrease(_floor - _target); 276 } 277 } 278 } 279 280 void Zone::requestIncrease(uint64_t targetDelta) 281 { 282 // Only increase when delta is higher than the current increase delta for 283 // the zone and currently under ceiling 284 if (targetDelta > _incDelta && _target < _ceiling) 285 { 286 auto requestTarget = getRequestTargetBase(); 287 requestTarget = (targetDelta - _incDelta) + requestTarget; 288 _incDelta = targetDelta; 289 // Target can not go above a current ceiling 290 if (requestTarget > _ceiling) 291 { 292 requestTarget = _ceiling; 293 } 294 setTarget(requestTarget); 295 // Restart timer countdown for fan target increase 296 _incTimer.restartOnce(_incDelay); 297 } 298 } 299 300 void Zone::incTimerExpired() 301 { 302 // Clear increase delta when timer expires allowing additional target 303 // increase requests or target decreases to occur 304 _incDelta = 0; 305 } 306 307 void Zone::requestDecrease(uint64_t targetDelta) 308 { 309 // Only decrease the lowest target delta requested 310 if (_decDelta == 0 || targetDelta < _decDelta) 311 { 312 _decDelta = targetDelta; 313 } 314 } 315 316 void Zone::decTimerExpired() 317 { 318 // Check all entries are set to allow a decrease 319 auto pred = [](auto const& entry) { return entry.second; }; 320 auto decAllowed = std::all_of(_decAllowed.begin(), _decAllowed.end(), pred); 321 322 // Only decrease targets when allowed, a requested decrease target delta 323 // exists, where no requested increases exist and the increase timer is not 324 // running (i.e. not in the middle of increasing) 325 if (decAllowed && _decDelta != 0 && _incDelta == 0 && 326 !_incTimer.isEnabled()) 327 { 328 auto requestTarget = getRequestTargetBase(); 329 // Request target should not start above ceiling 330 if (requestTarget > _ceiling) 331 { 332 requestTarget = _ceiling; 333 } 334 // Target can not go below the defined floor 335 if ((requestTarget < _decDelta) || (requestTarget - _decDelta < _floor)) 336 { 337 requestTarget = _floor; 338 } 339 else 340 { 341 requestTarget = requestTarget - _decDelta; 342 } 343 setTarget(requestTarget); 344 } 345 // Clear decrease delta when timer expires 346 _decDelta = 0; 347 // Decrease timer is restarted since its repeating 348 } 349 350 void Zone::setPersisted(const std::string& intf, const std::string& prop) 351 { 352 if (std::find_if(_propsPersisted[intf].begin(), _propsPersisted[intf].end(), 353 [&prop](const auto& p) { return prop == p; }) == 354 _propsPersisted[intf].end()) 355 { 356 _propsPersisted[intf].emplace_back(prop); 357 } 358 } 359 360 bool Zone::isPersisted(const std::string& intf, const std::string& prop) const 361 { 362 auto it = _propsPersisted.find(intf); 363 if (it == _propsPersisted.end()) 364 { 365 return false; 366 } 367 368 return std::any_of(it->second.begin(), it->second.end(), 369 [&prop](const auto& p) { return prop == p; }); 370 } 371 372 void Zone::setPowerOnTarget(const json& jsonObj) 373 { 374 if (!jsonObj.contains("poweron_target")) 375 { 376 auto msg = "Missing required zone's poweron target"; 377 log<level::ERR>(msg, entry("JSON=%s", jsonObj.dump().c_str())); 378 throw std::runtime_error(msg); 379 } 380 _poweronTarget = jsonObj["poweron_target"].get<uint64_t>(); 381 } 382 383 void Zone::setInterfaces(const json& jsonObj) 384 { 385 for (const auto& interface : jsonObj["interfaces"]) 386 { 387 if (!interface.contains("name") || !interface.contains("properties")) 388 { 389 log<level::ERR>("Missing required zone interface attributes", 390 entry("JSON=%s", interface.dump().c_str())); 391 throw std::runtime_error( 392 "Missing required zone interface attributes"); 393 } 394 auto propFuncs = 395 _intfPropHandlers.find(interface["name"].get<std::string>()); 396 if (propFuncs == _intfPropHandlers.end()) 397 { 398 // Construct list of available configurable interfaces 399 auto intfs = std::accumulate( 400 std::next(_intfPropHandlers.begin()), _intfPropHandlers.end(), 401 _intfPropHandlers.begin()->first, [](auto list, auto intf) { 402 return std::move(list) + ", " + intf.first; 403 }); 404 log<level::ERR>("Configured interface not available", 405 entry("JSON=%s", interface.dump().c_str()), 406 entry("AVAILABLE_INTFS=%s", intfs.c_str())); 407 throw std::runtime_error("Configured interface not available"); 408 } 409 410 for (const auto& property : interface["properties"]) 411 { 412 if (!property.contains("name")) 413 { 414 log<level::ERR>( 415 "Missing required interface property attributes", 416 entry("JSON=%s", property.dump().c_str())); 417 throw std::runtime_error( 418 "Missing required interface property attributes"); 419 } 420 // Attribute "persist" is optional, defaults to `false` 421 auto persist = false; 422 if (property.contains("persist")) 423 { 424 persist = property["persist"].get<bool>(); 425 } 426 // Property name from JSON must exactly match supported 427 // index names to functions in property namespace 428 auto propFunc = 429 propFuncs->second.find(property["name"].get<std::string>()); 430 if (propFunc == propFuncs->second.end()) 431 { 432 // Construct list of available configurable properties 433 auto props = std::accumulate( 434 std::next(propFuncs->second.begin()), 435 propFuncs->second.end(), propFuncs->second.begin()->first, 436 [](auto list, auto prop) { 437 return std::move(list) + ", " + prop.first; 438 }); 439 log<level::ERR>("Configured property not available", 440 entry("JSON=%s", property.dump().c_str()), 441 entry("AVAILABLE_PROPS=%s", props.c_str())); 442 throw std::runtime_error( 443 "Configured property function not available"); 444 } 445 446 _propInitFunctions.emplace_back( 447 propFunc->second(property, persist)); 448 } 449 } 450 } 451 452 json Zone::dump() const 453 { 454 json output; 455 456 output["active"] = _isActive; 457 output["floor"] = _floor; 458 output["ceiling"] = _ceiling; 459 output["target"] = _target; 460 output["increase_delta"] = _incDelta; 461 output["decrease_delta"] = _decDelta; 462 output["power_on_target"] = _poweronTarget; 463 output["default_ceiling"] = _defaultCeiling; 464 output["default_floor"] = _defaultFloor; 465 output["increase_delay"] = _incDelay.count(); 466 output["decrease_interval"] = _decInterval.count(); 467 output["requested_target_base"] = _requestTargetBase; 468 output["floor_change"] = _floorChange; 469 output["decrease_allowed"] = _decAllowed; 470 output["persisted_props"] = _propsPersisted; 471 output["target_holds"] = _targetHolds; 472 output["floor_holds"] = _floorHolds; 473 474 return output; 475 } 476 477 /** 478 * Properties of interfaces supported by the zone configuration that return 479 * a handler function that sets the zone's property value(s) and persist 480 * state. 481 */ 482 namespace zone::property 483 { 484 // Get a set property handler function for the configured values of the 485 // "Supported" property 486 std::function<void(DBusZone&, Zone&)> supported(const json& jsonObj, 487 bool persist) 488 { 489 std::vector<std::string> values; 490 if (!jsonObj.contains("values")) 491 { 492 log<level::ERR>("No 'values' found for \"Supported\" property, " 493 "using an empty list", 494 entry("JSON=%s", jsonObj.dump().c_str())); 495 } 496 else 497 { 498 for (const auto& value : jsonObj["values"]) 499 { 500 if (!value.contains("value")) 501 { 502 log<level::ERR>("No 'value' found for \"Supported\" property " 503 "entry, skipping", 504 entry("JSON=%s", value.dump().c_str())); 505 } 506 else 507 { 508 values.emplace_back(value["value"].get<std::string>()); 509 } 510 } 511 } 512 513 return Zone::setProperty<std::vector<std::string>>( 514 DBusZone::thermalModeIntf, DBusZone::supportedProp, 515 &DBusZone::supported, std::move(values), persist); 516 } 517 518 // Get a set property handler function for a configured value of the 519 // "Current" property 520 std::function<void(DBusZone&, Zone&)> current(const json& jsonObj, bool persist) 521 { 522 // Use default value for "Current" property if no "value" entry given 523 if (!jsonObj.contains("value")) 524 { 525 log<level::INFO>("No 'value' found for \"Current\" property, " 526 "using default", 527 entry("JSON=%s", jsonObj.dump().c_str())); 528 // Set persist state of property 529 return Zone::setPropertyPersist(DBusZone::thermalModeIntf, 530 DBusZone::currentProp, persist); 531 } 532 533 return Zone::setProperty<std::string>( 534 DBusZone::thermalModeIntf, DBusZone::currentProp, &DBusZone::current, 535 jsonObj["value"].get<std::string>(), persist); 536 } 537 538 } // namespace zone::property 539 540 } // namespace phosphor::fan::control::json 541