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 "dbus_zone.hpp" 19 #include "fan.hpp" 20 #include "sdbusplus.hpp" 21 22 #include <nlohmann/json.hpp> 23 #include <phosphor-logging/log.hpp> 24 #include <sdeventplus/event.hpp> 25 26 #include <algorithm> 27 #include <chrono> 28 #include <iterator> 29 #include <map> 30 #include <memory> 31 #include <numeric> 32 #include <utility> 33 #include <vector> 34 35 namespace phosphor::fan::control::json 36 { 37 38 using json = nlohmann::json; 39 using namespace phosphor::logging; 40 41 const std::map< 42 std::string, 43 std::map<std::string, std::function<std::function<void(DBusZone&, Zone&)>( 44 const json&, bool)>>> 45 Zone::_intfPropHandlers = { 46 {DBusZone::thermalModeIntf, 47 {{DBusZone::supportedProp, zone::property::supported}, 48 {DBusZone::currentProp, zone::property::current}}}}; 49 50 Zone::Zone(const json& jsonObj, const sdeventplus::Event& event, Manager* mgr) : 51 ConfigBase(jsonObj), _dbusZone{}, _manager(mgr), _defaultFloor(0), 52 _incDelay(0), _decInterval(0), _floor(0), _target(0), _incDelta(0), 53 _decDelta(0), _requestTargetBase(0), _isActive(true), 54 _incTimer(event, std::bind(&Zone::incTimerExpired, this)), 55 _decTimer(event, std::bind(&Zone::decTimerExpired, this)) 56 { 57 // Increase delay is optional, defaults to 0 58 if (jsonObj.contains("increase_delay")) 59 { 60 _incDelay = 61 std::chrono::seconds(jsonObj["increase_delay"].get<uint64_t>()); 62 } 63 64 // Poweron target is required 65 setPowerOnTarget(jsonObj); 66 67 // Default ceiling is optional, defaults to poweron target 68 _defaultCeiling = _poweronTarget; 69 if (jsonObj.contains("default_ceiling")) 70 { 71 _defaultCeiling = jsonObj["default_ceiling"].get<uint64_t>(); 72 } 73 // Start with the current ceiling set as the default ceiling 74 _ceiling = _defaultCeiling; 75 76 // Default floor is optional, defaults to 0 77 if (jsonObj.contains("default_floor")) 78 { 79 _defaultFloor = jsonObj["default_floor"].get<uint64_t>(); 80 // Start with the current floor set as the default 81 _floor = _defaultFloor; 82 } 83 84 // Decrease interval is optional, defaults to 0 85 // A decrease interval of 0sec disables the decrease timer 86 if (jsonObj.contains("decrease_interval")) 87 { 88 _decInterval = 89 std::chrono::seconds(jsonObj["decrease_interval"].get<uint64_t>()); 90 } 91 92 // Setting properties on interfaces to be served are optional 93 if (jsonObj.contains("interfaces")) 94 { 95 setInterfaces(jsonObj); 96 } 97 } 98 99 void Zone::enable() 100 { 101 // Create thermal control dbus object 102 _dbusZone = std::make_unique<DBusZone>(*this); 103 104 // Init all configured dbus interfaces' property states 105 for (const auto& func : _propInitFunctions) 106 { 107 // Only call non-null init property functions 108 if (func) 109 { 110 func(*_dbusZone, *this); 111 } 112 } 113 114 // TODO - Restore any persisted properties in init function 115 // Restore thermal control current mode state, if exists 116 _dbusZone->restoreCurrentMode(); 117 118 // Emit object added for this zone's associated dbus object 119 _dbusZone->emit_object_added(); 120 121 // A decrease interval of 0sec disables the decrease timer 122 if (_decInterval != std::chrono::seconds::zero()) 123 { 124 // Start timer for fan target decreases 125 _decTimer.restart(_decInterval); 126 } 127 } 128 129 void Zone::addFan(std::unique_ptr<Fan> fan) 130 { 131 _fans.emplace_back(std::move(fan)); 132 } 133 134 void Zone::setTarget(uint64_t target) 135 { 136 if (_isActive && _target != target) 137 { 138 _target = target; 139 for (auto& fan : _fans) 140 { 141 fan->setTarget(_target); 142 } 143 } 144 } 145 146 void Zone::setActiveAllow(const std::string& ident, bool isActiveAllow) 147 { 148 _active[ident] = isActiveAllow; 149 if (!isActiveAllow) 150 { 151 _isActive = false; 152 } 153 else 154 { 155 // Check all entries are set to allow active fan control 156 auto actPred = [](const auto& entry) { return entry.second; }; 157 _isActive = std::all_of(_active.begin(), _active.end(), actPred); 158 } 159 } 160 161 void Zone::setFloor(uint64_t target) 162 { 163 // Check all entries are set to allow floor to be set 164 auto pred = [](const auto& entry) { return entry.second; }; 165 if (std::all_of(_floorChange.begin(), _floorChange.end(), pred)) 166 { 167 _floor = target; 168 // Floor above target, update target to floor 169 if (_target < _floor) 170 { 171 requestIncrease(_floor - _target); 172 } 173 } 174 } 175 176 void Zone::requestIncrease(uint64_t targetDelta) 177 { 178 // Only increase when delta is higher than the current increase delta for 179 // the zone and currently under ceiling 180 if (targetDelta > _incDelta && _target < _ceiling) 181 { 182 auto requestTarget = getRequestTargetBase(); 183 requestTarget = (targetDelta - _incDelta) + requestTarget; 184 _incDelta = targetDelta; 185 // Target can not go above a current ceiling 186 if (requestTarget > _ceiling) 187 { 188 requestTarget = _ceiling; 189 } 190 setTarget(requestTarget); 191 // Restart timer countdown for fan target increase 192 _incTimer.restartOnce(_incDelay); 193 } 194 } 195 196 void Zone::incTimerExpired() 197 { 198 // Clear increase delta when timer expires allowing additional target 199 // increase requests or target decreases to occur 200 _incDelta = 0; 201 } 202 203 void Zone::requestDecrease(uint64_t targetDelta) 204 { 205 // Only decrease the lowest target delta requested 206 if (_decDelta == 0 || targetDelta < _decDelta) 207 { 208 _decDelta = targetDelta; 209 } 210 } 211 212 void Zone::decTimerExpired() 213 { 214 // Check all entries are set to allow a decrease 215 auto pred = [](auto const& entry) { return entry.second; }; 216 auto decAllowed = std::all_of(_decAllowed.begin(), _decAllowed.end(), pred); 217 218 // Only decrease targets when allowed, a requested decrease target delta 219 // exists, where no requested increases exist and the increase timer is not 220 // running (i.e. not in the middle of increasing) 221 if (decAllowed && _decDelta != 0 && _incDelta == 0 && 222 !_incTimer.isEnabled()) 223 { 224 auto requestTarget = getRequestTargetBase(); 225 // Request target should not start above ceiling 226 if (requestTarget > _ceiling) 227 { 228 requestTarget = _ceiling; 229 } 230 // Target can not go below the defined floor 231 if ((requestTarget < _decDelta) || (requestTarget - _decDelta < _floor)) 232 { 233 requestTarget = _floor; 234 } 235 else 236 { 237 requestTarget = requestTarget - _decDelta; 238 } 239 setTarget(requestTarget); 240 } 241 // Clear decrease delta when timer expires 242 _decDelta = 0; 243 // Decrease timer is restarted since its repeating 244 } 245 246 void Zone::setPersisted(const std::string& intf, const std::string& prop) 247 { 248 if (std::find_if(_propsPersisted[intf].begin(), _propsPersisted[intf].end(), 249 [&prop](const auto& p) { return prop == p; }) == 250 _propsPersisted[intf].end()) 251 { 252 _propsPersisted[intf].emplace_back(prop); 253 } 254 } 255 256 bool Zone::isPersisted(const std::string& intf, const std::string& prop) const 257 { 258 auto it = _propsPersisted.find(intf); 259 if (it == _propsPersisted.end()) 260 { 261 return false; 262 } 263 264 return std::any_of(it->second.begin(), it->second.end(), 265 [&prop](const auto& p) { return prop == p; }); 266 } 267 268 void Zone::setPowerOnTarget(const json& jsonObj) 269 { 270 // TODO Remove "full_speed" after configs replaced with "poweron_target" 271 if (!jsonObj.contains("full_speed") && !jsonObj.contains("poweron_target")) 272 { 273 auto msg = "Missing required zone's poweron target"; 274 log<level::ERR>(msg, entry("JSON=%s", jsonObj.dump().c_str())); 275 throw std::runtime_error(msg); 276 } 277 if (jsonObj.contains("full_speed")) 278 { 279 _poweronTarget = jsonObj["full_speed"].get<uint64_t>(); 280 } 281 else 282 { 283 _poweronTarget = jsonObj["poweron_target"].get<uint64_t>(); 284 } 285 // Start with the current target set as the poweron target 286 _target = _poweronTarget; 287 } 288 289 void Zone::setInterfaces(const json& jsonObj) 290 { 291 for (const auto& interface : jsonObj["interfaces"]) 292 { 293 if (!interface.contains("name") || !interface.contains("properties")) 294 { 295 log<level::ERR>("Missing required zone interface attributes", 296 entry("JSON=%s", interface.dump().c_str())); 297 throw std::runtime_error( 298 "Missing required zone interface attributes"); 299 } 300 auto propFuncs = 301 _intfPropHandlers.find(interface["name"].get<std::string>()); 302 if (propFuncs == _intfPropHandlers.end()) 303 { 304 // Construct list of available configurable interfaces 305 auto intfs = std::accumulate( 306 std::next(_intfPropHandlers.begin()), _intfPropHandlers.end(), 307 _intfPropHandlers.begin()->first, [](auto list, auto intf) { 308 return std::move(list) + ", " + intf.first; 309 }); 310 log<level::ERR>("Configured interface not available", 311 entry("JSON=%s", interface.dump().c_str()), 312 entry("AVAILABLE_INTFS=%s", intfs.c_str())); 313 throw std::runtime_error("Configured interface not available"); 314 } 315 316 for (const auto& property : interface["properties"]) 317 { 318 if (!property.contains("name")) 319 { 320 log<level::ERR>( 321 "Missing required interface property attributes", 322 entry("JSON=%s", property.dump().c_str())); 323 throw std::runtime_error( 324 "Missing required interface property attributes"); 325 } 326 // Attribute "persist" is optional, defaults to `false` 327 auto persist = false; 328 if (property.contains("persist")) 329 { 330 persist = property["persist"].get<bool>(); 331 } 332 // Property name from JSON must exactly match supported 333 // index names to functions in property namespace 334 auto propFunc = 335 propFuncs->second.find(property["name"].get<std::string>()); 336 if (propFunc == propFuncs->second.end()) 337 { 338 // Construct list of available configurable properties 339 auto props = std::accumulate( 340 std::next(propFuncs->second.begin()), 341 propFuncs->second.end(), propFuncs->second.begin()->first, 342 [](auto list, auto prop) { 343 return std::move(list) + ", " + prop.first; 344 }); 345 log<level::ERR>("Configured property not available", 346 entry("JSON=%s", property.dump().c_str()), 347 entry("AVAILABLE_PROPS=%s", props.c_str())); 348 throw std::runtime_error( 349 "Configured property function not available"); 350 } 351 352 _propInitFunctions.emplace_back( 353 propFunc->second(property, persist)); 354 } 355 } 356 } 357 358 /** 359 * Properties of interfaces supported by the zone configuration that return 360 * a handler function that sets the zone's property value(s) and persist 361 * state. 362 */ 363 namespace zone::property 364 { 365 // Get a set property handler function for the configured values of the 366 // "Supported" property 367 std::function<void(DBusZone&, Zone&)> supported(const json& jsonObj, 368 bool persist) 369 { 370 std::vector<std::string> values; 371 if (!jsonObj.contains("values")) 372 { 373 log<level::ERR>("No 'values' found for \"Supported\" property, " 374 "using an empty list", 375 entry("JSON=%s", jsonObj.dump().c_str())); 376 } 377 else 378 { 379 for (const auto& value : jsonObj["values"]) 380 { 381 if (!value.contains("value")) 382 { 383 log<level::ERR>("No 'value' found for \"Supported\" property " 384 "entry, skipping", 385 entry("JSON=%s", value.dump().c_str())); 386 } 387 else 388 { 389 values.emplace_back(value["value"].get<std::string>()); 390 } 391 } 392 } 393 394 return Zone::setProperty<std::vector<std::string>>( 395 DBusZone::thermalModeIntf, DBusZone::supportedProp, 396 &DBusZone::supported, std::move(values), persist); 397 } 398 399 // Get a set property handler function for a configured value of the 400 // "Current" property 401 std::function<void(DBusZone&, Zone&)> current(const json& jsonObj, bool persist) 402 { 403 // Use default value for "Current" property if no "value" entry given 404 if (!jsonObj.contains("value")) 405 { 406 log<level::INFO>("No 'value' found for \"Current\" property, " 407 "using default", 408 entry("JSON=%s", jsonObj.dump().c_str())); 409 // Set persist state of property 410 return Zone::setPropertyPersist(DBusZone::thermalModeIntf, 411 DBusZone::currentProp, persist); 412 } 413 414 return Zone::setProperty<std::string>( 415 DBusZone::thermalModeIntf, DBusZone::currentProp, &DBusZone::current, 416 jsonObj["value"].get<std::string>(), persist); 417 } 418 419 } // namespace zone::property 420 421 } // namespace phosphor::fan::control::json 422