blob: 434931ab81bc55f0a18552a553fa2668cb7ef4ae [file] [log] [blame]
Matthew Barth4f0d3b72020-08-27 14:32:15 -05001/**
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
Matthew Barthbc89a8a2021-05-25 15:42:58 -050018#include "dbus_zone.hpp"
Matthew Barthde90fb42021-03-04 16:34:28 -060019#include "fan.hpp"
Matthew Barth9403a212021-05-17 09:31:50 -050020#include "sdbusplus.hpp"
Matthew Barth216229c2020-09-24 13:47:33 -050021
Matthew Barth4f0d3b72020-08-27 14:32:15 -050022#include <nlohmann/json.hpp>
23#include <phosphor-logging/log.hpp>
Matthew Barth603ef162021-03-24 15:34:53 -050024#include <sdeventplus/event.hpp>
Matthew Barth4f0d3b72020-08-27 14:32:15 -050025
Matthew Bartha0dd1352021-03-09 11:10:49 -060026#include <algorithm>
Matthew Barth007de092021-03-25 13:56:04 -050027#include <chrono>
Matthew Barth651f03a2020-08-27 16:15:11 -050028#include <iterator>
29#include <map>
Matthew Barthbc89a8a2021-05-25 15:42:58 -050030#include <memory>
Matthew Barth651f03a2020-08-27 16:15:11 -050031#include <numeric>
Matthew Barth651f03a2020-08-27 16:15:11 -050032#include <utility>
Matthew Barth216229c2020-09-24 13:47:33 -050033#include <vector>
Matthew Barth651f03a2020-08-27 16:15:11 -050034
Matthew Barth4f0d3b72020-08-27 14:32:15 -050035namespace phosphor::fan::control::json
36{
37
38using json = nlohmann::json;
39using namespace phosphor::logging;
40
Matthew Barthbc89a8a2021-05-25 15:42:58 -050041const 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}}}};
Matthew Barth651f03a2020-08-27 16:15:11 -050049
Matthew Barth9403a212021-05-17 09:31:50 -050050Zone::Zone(const json& jsonObj, const sdeventplus::Event& event, Manager* mgr) :
Matthew Barthbc89a8a2021-05-25 15:42:58 -050051 ConfigBase(jsonObj), _dbusZone{}, _manager(mgr), _incDelay(0), _floor(0),
52 _target(0), _incDelta(0), _decDelta(0), _requestTargetBase(0),
53 _isActive(true), _incTimer(event, std::bind(&Zone::incTimerExpired, this)),
Matthew Barth007de092021-03-25 13:56:04 -050054 _decTimer(event, std::bind(&Zone::decTimerExpired, this))
Matthew Barth4f0d3b72020-08-27 14:32:15 -050055{
Matthew Barthe47c9582021-03-09 14:24:02 -060056 // Increase delay is optional, defaults to 0
Matthew Barth4f0d3b72020-08-27 14:32:15 -050057 if (jsonObj.contains("increase_delay"))
58 {
Matthew Barth007de092021-03-25 13:56:04 -050059 _incDelay =
60 std::chrono::seconds(jsonObj["increase_delay"].get<uint64_t>());
Matthew Barth4f0d3b72020-08-27 14:32:15 -050061 }
Matthew Barthe47c9582021-03-09 14:24:02 -060062 setDefaultCeiling(jsonObj);
Matthew Barth4f0d3b72020-08-27 14:32:15 -050063 setDefaultFloor(jsonObj);
64 setDecInterval(jsonObj);
Matthew Barth651f03a2020-08-27 16:15:11 -050065 // Setting properties on interfaces to be served are optional
66 if (jsonObj.contains("interfaces"))
67 {
68 setInterfaces(jsonObj);
69 }
Matthew Barth14303a42021-05-21 13:04:08 -050070}
Matthew Barth007de092021-03-25 13:56:04 -050071
Matthew Barth14303a42021-05-21 13:04:08 -050072void Zone::enable()
73{
Matthew Barthbc89a8a2021-05-25 15:42:58 -050074 // Create thermal control dbus object
75 _dbusZone = std::make_unique<DBusZone>(*this);
Matthew Bartha4483742021-03-25 14:09:01 -050076
Matthew Barthbc89a8a2021-05-25 15:42:58 -050077 // Init all configured dbus interfaces' property states
78 for (const auto& func : _propInitFunctions)
79 {
80 // Only call non-null init property functions
81 if (func)
82 {
83 func(*_dbusZone, *this);
84 }
85 }
86
87 // TODO - Restore any persisted properties in init function
88 // Restore thermal control current mode state, if exists
89 _dbusZone->restoreCurrentMode();
90
91 // Emit object added for this zone's associated dbus object
92 _dbusZone->emit_object_added();
Matthew Bartha4483742021-03-25 14:09:01 -050093
Matthew Barth007de092021-03-25 13:56:04 -050094 // Start timer for fan target decreases
95 _decTimer.restart(_decInterval);
Matthew Barth4f0d3b72020-08-27 14:32:15 -050096}
97
Matthew Barthde90fb42021-03-04 16:34:28 -060098void Zone::addFan(std::unique_ptr<Fan> fan)
99{
100 _fans.emplace_back(std::move(fan));
101}
102
Matthew Barth8ba715e2021-03-05 09:00:05 -0600103void Zone::setTarget(uint64_t target)
104{
Matthew Barth6f787302021-03-25 15:01:01 -0500105 if (_isActive && _target != target)
Matthew Barth8ba715e2021-03-05 09:00:05 -0600106 {
107 _target = target;
108 for (auto& fan : _fans)
109 {
110 fan->setTarget(_target);
111 }
112 }
113}
114
115void Zone::setActiveAllow(const std::string& ident, bool isActiveAllow)
116{
117 _active[ident] = isActiveAllow;
118 if (!isActiveAllow)
119 {
120 _isActive = false;
121 }
122 else
123 {
124 // Check all entries are set to allow active fan control
125 auto actPred = [](const auto& entry) { return entry.second; };
126 _isActive = std::all_of(_active.begin(), _active.end(), actPred);
127 }
128}
129
Matthew Barth12cb1252021-03-08 16:47:30 -0600130void Zone::setFloor(uint64_t target)
131{
132 // Check all entries are set to allow floor to be set
Matthew Barth8ba715e2021-03-05 09:00:05 -0600133 auto pred = [](const auto& entry) { return entry.second; };
Matthew Barth12cb1252021-03-08 16:47:30 -0600134 if (std::all_of(_floorChange.begin(), _floorChange.end(), pred))
135 {
136 _floor = target;
137 // Floor above target, update target to floor
138 if (_target < _floor)
139 {
140 requestIncrease(_floor - _target);
141 }
142 }
143}
144
145void Zone::requestIncrease(uint64_t targetDelta)
146{
Matthew Barth2b3253e2021-03-09 14:51:16 -0600147 // Only increase when delta is higher than the current increase delta for
148 // the zone and currently under ceiling
149 if (targetDelta > _incDelta && _target < _ceiling)
150 {
151 auto requestTarget = getRequestTargetBase();
152 requestTarget = (targetDelta - _incDelta) + requestTarget;
153 _incDelta = targetDelta;
154 // Target can not go above a current ceiling
155 if (requestTarget > _ceiling)
156 {
157 requestTarget = _ceiling;
158 }
Matthew Barth8ba715e2021-03-05 09:00:05 -0600159 setTarget(requestTarget);
Matthew Barth007de092021-03-25 13:56:04 -0500160 // Restart timer countdown for fan target increase
161 _incTimer.restartOnce(_incDelay);
Matthew Barth2b3253e2021-03-09 14:51:16 -0600162 }
Matthew Barth12cb1252021-03-08 16:47:30 -0600163}
164
Matthew Barth007de092021-03-25 13:56:04 -0500165void Zone::incTimerExpired()
166{
167 // Clear increase delta when timer expires allowing additional target
168 // increase requests or target decreases to occur
169 _incDelta = 0;
170}
171
Matthew Barth45c44ea2021-03-03 13:16:14 -0600172void Zone::requestDecrease(uint64_t targetDelta)
173{
174 // Only decrease the lowest target delta requested
175 if (_decDelta == 0 || targetDelta < _decDelta)
176 {
177 _decDelta = targetDelta;
178 }
179}
180
Matthew Barth007de092021-03-25 13:56:04 -0500181void Zone::decTimerExpired()
182{
183 // Check all entries are set to allow a decrease
184 auto pred = [](auto const& entry) { return entry.second; };
185 auto decAllowed = std::all_of(_decAllowed.begin(), _decAllowed.end(), pred);
186
187 // Only decrease targets when allowed, a requested decrease target delta
188 // exists, where no requested increases exist and the increase timer is not
189 // running (i.e. not in the middle of increasing)
190 if (decAllowed && _decDelta != 0 && _incDelta == 0 &&
191 !_incTimer.isEnabled())
192 {
193 auto requestTarget = getRequestTargetBase();
194 // Request target should not start above ceiling
195 if (requestTarget > _ceiling)
196 {
197 requestTarget = _ceiling;
198 }
199 // Target can not go below the defined floor
200 if ((requestTarget < _decDelta) || (requestTarget - _decDelta < _floor))
201 {
202 requestTarget = _floor;
203 }
204 else
205 {
206 requestTarget = requestTarget - _decDelta;
207 }
208 setTarget(requestTarget);
209 }
210 // Clear decrease delta when timer expires
211 _decDelta = 0;
212 // Decrease timer is restarted since its repeating
213}
214
Matthew Bartha0dd1352021-03-09 11:10:49 -0600215void Zone::setPersisted(const std::string& intf, const std::string& prop)
216{
217 if (std::find_if(_propsPersisted[intf].begin(), _propsPersisted[intf].end(),
Matthew Barthbc89a8a2021-05-25 15:42:58 -0500218 [&prop](const auto& p) { return prop == p; }) ==
Matthew Bartha0dd1352021-03-09 11:10:49 -0600219 _propsPersisted[intf].end())
220 {
221 _propsPersisted[intf].emplace_back(prop);
222 }
223}
224
Matthew Barth279183f2021-05-25 10:19:43 -0500225bool Zone::isPersisted(const std::string& intf, const std::string& prop) const
226{
227 auto it = _propsPersisted.find(intf);
228 if (it == _propsPersisted.end())
229 {
230 return false;
231 }
232
233 return std::any_of(it->second.begin(), it->second.end(),
234 [&prop](const auto& p) { return prop == p; });
235}
236
Matthew Barthe47c9582021-03-09 14:24:02 -0600237void Zone::setDefaultCeiling(const json& jsonObj)
Matthew Barth4f0d3b72020-08-27 14:32:15 -0500238{
Matthew Barth5606ec82021-05-17 12:08:52 -0500239 // TODO Remove "full_speed" after configs replaced with "default_ceiling"
240 if (!jsonObj.contains("full_speed") && !jsonObj.contains("default_ceiling"))
Matthew Barth4f0d3b72020-08-27 14:32:15 -0500241 {
Matthew Barth5606ec82021-05-17 12:08:52 -0500242 log<level::ERR>("Missing required zone's default ceiling",
Matthew Barth4f0d3b72020-08-27 14:32:15 -0500243 entry("JSON=%s", jsonObj.dump().c_str()));
Matthew Barth5606ec82021-05-17 12:08:52 -0500244 throw std::runtime_error("Missing required zone's default ceiling");
Matthew Barth4f0d3b72020-08-27 14:32:15 -0500245 }
Matthew Barth5606ec82021-05-17 12:08:52 -0500246 if (jsonObj.contains("full_speed"))
247 {
248 _defaultCeiling = jsonObj["full_speed"].get<uint64_t>();
249 }
250 else
251 {
252 _defaultCeiling = jsonObj["default_ceiling"].get<uint64_t>();
253 }
Matthew Barth12cb1252021-03-08 16:47:30 -0600254 // Start with the current target set as the default
Matthew Barthe47c9582021-03-09 14:24:02 -0600255 _target = _defaultCeiling;
Matthew Barth2b3253e2021-03-09 14:51:16 -0600256 // Start with the current ceiling set as the default
257 _ceiling = _defaultCeiling;
Matthew Barth4f0d3b72020-08-27 14:32:15 -0500258}
259
260void Zone::setDefaultFloor(const json& jsonObj)
261{
262 if (!jsonObj.contains("default_floor"))
263 {
Matthew Barthe47c9582021-03-09 14:24:02 -0600264 log<level::ERR>("Missing required zone's default floor",
Matthew Barth4f0d3b72020-08-27 14:32:15 -0500265 entry("JSON=%s", jsonObj.dump().c_str()));
Matthew Barthe47c9582021-03-09 14:24:02 -0600266 throw std::runtime_error("Missing required zone's default floor");
Matthew Barth4f0d3b72020-08-27 14:32:15 -0500267 }
268 _defaultFloor = jsonObj["default_floor"].get<uint64_t>();
Matthew Barth12cb1252021-03-08 16:47:30 -0600269 // Start with the current floor set as the default
270 _floor = _defaultFloor;
Matthew Barth4f0d3b72020-08-27 14:32:15 -0500271}
272
273void Zone::setDecInterval(const json& jsonObj)
274{
275 if (!jsonObj.contains("decrease_interval"))
276 {
277 log<level::ERR>("Missing required zone's decrease interval",
278 entry("JSON=%s", jsonObj.dump().c_str()));
279 throw std::runtime_error("Missing required zone's decrease interval");
280 }
Matthew Barth007de092021-03-25 13:56:04 -0500281 _decInterval =
282 std::chrono::seconds(jsonObj["decrease_interval"].get<uint64_t>());
Matthew Barth4f0d3b72020-08-27 14:32:15 -0500283}
284
Matthew Barth651f03a2020-08-27 16:15:11 -0500285void Zone::setInterfaces(const json& jsonObj)
286{
287 for (const auto& interface : jsonObj["interfaces"])
288 {
289 if (!interface.contains("name") || !interface.contains("properties"))
290 {
291 log<level::ERR>("Missing required zone interface attributes",
292 entry("JSON=%s", interface.dump().c_str()));
293 throw std::runtime_error(
294 "Missing required zone interface attributes");
295 }
Matthew Barth216229c2020-09-24 13:47:33 -0500296 auto propFuncs =
297 _intfPropHandlers.find(interface["name"].get<std::string>());
298 if (propFuncs == _intfPropHandlers.end())
299 {
300 // Construct list of available configurable interfaces
301 auto intfs = std::accumulate(
302 std::next(_intfPropHandlers.begin()), _intfPropHandlers.end(),
303 _intfPropHandlers.begin()->first, [](auto list, auto intf) {
304 return std::move(list) + ", " + intf.first;
305 });
306 log<level::ERR>("Configured interface not available",
307 entry("JSON=%s", interface.dump().c_str()),
308 entry("AVAILABLE_INTFS=%s", intfs.c_str()));
309 throw std::runtime_error("Configured interface not available");
310 }
311
Matthew Barth651f03a2020-08-27 16:15:11 -0500312 for (const auto& property : interface["properties"])
313 {
314 if (!property.contains("name"))
315 {
316 log<level::ERR>(
317 "Missing required interface property attributes",
318 entry("JSON=%s", property.dump().c_str()));
319 throw std::runtime_error(
320 "Missing required interface property attributes");
321 }
322 // Attribute "persist" is optional, defaults to `false`
323 auto persist = false;
324 if (property.contains("persist"))
325 {
326 persist = property["persist"].get<bool>();
327 }
328 // Property name from JSON must exactly match supported
329 // index names to functions in property namespace
Matthew Barth216229c2020-09-24 13:47:33 -0500330 auto propFunc =
331 propFuncs->second.find(property["name"].get<std::string>());
332 if (propFunc == propFuncs->second.end())
Matthew Barth651f03a2020-08-27 16:15:11 -0500333 {
Matthew Barth216229c2020-09-24 13:47:33 -0500334 // Construct list of available configurable properties
Matthew Barth651f03a2020-08-27 16:15:11 -0500335 auto props = std::accumulate(
Matthew Barth216229c2020-09-24 13:47:33 -0500336 std::next(propFuncs->second.begin()),
337 propFuncs->second.end(), propFuncs->second.begin()->first,
338 [](auto list, auto prop) {
Matthew Barth651f03a2020-08-27 16:15:11 -0500339 return std::move(list) + ", " + prop.first;
340 });
Matthew Barth216229c2020-09-24 13:47:33 -0500341 log<level::ERR>("Configured property not available",
Matthew Barth651f03a2020-08-27 16:15:11 -0500342 entry("JSON=%s", property.dump().c_str()),
343 entry("AVAILABLE_PROPS=%s", props.c_str()));
344 throw std::runtime_error(
345 "Configured property function not available");
346 }
Matthew Barthbc89a8a2021-05-25 15:42:58 -0500347
348 _propInitFunctions.emplace_back(
349 propFunc->second(property, persist));
Matthew Barth651f03a2020-08-27 16:15:11 -0500350 }
Matthew Barth651f03a2020-08-27 16:15:11 -0500351 }
352}
353
354/**
Matthew Barth216229c2020-09-24 13:47:33 -0500355 * Properties of interfaces supported by the zone configuration that return
Matthew Barthb584d812021-03-11 15:55:04 -0600356 * a handler function that sets the zone's property value(s) and persist state.
Matthew Barth651f03a2020-08-27 16:15:11 -0500357 */
358namespace zone::property
359{
Matthew Barthb584d812021-03-11 15:55:04 -0600360// Get a set property handler function for the configured values of the
361// "Supported" property
Matthew Barthbc89a8a2021-05-25 15:42:58 -0500362std::function<void(DBusZone&, Zone&)> supported(const json& jsonObj,
363 bool persist)
Matthew Barth651f03a2020-08-27 16:15:11 -0500364{
365 std::vector<std::string> values;
Matthew Barth216229c2020-09-24 13:47:33 -0500366 if (!jsonObj.contains("values"))
Matthew Barth651f03a2020-08-27 16:15:11 -0500367 {
Matthew Barth216229c2020-09-24 13:47:33 -0500368 log<level::ERR>(
369 "No 'values' found for \"Supported\" property, using an empty list",
370 entry("JSON=%s", jsonObj.dump().c_str()));
371 }
372 else
373 {
374 for (const auto& value : jsonObj["values"])
375 {
376 if (!value.contains("value"))
377 {
378 log<level::ERR>("No 'value' found for \"Supported\" property "
379 "entry, skipping",
380 entry("JSON=%s", value.dump().c_str()));
381 }
382 else
383 {
384 values.emplace_back(value["value"].get<std::string>());
385 }
386 }
Matthew Barth651f03a2020-08-27 16:15:11 -0500387 }
388
Matthew Barthb584d812021-03-11 15:55:04 -0600389 return Zone::setProperty<std::vector<std::string>>(
Matthew Barthbc89a8a2021-05-25 15:42:58 -0500390 DBusZone::thermalModeIntf, DBusZone::supportedProp,
391 &DBusZone::supported, std::move(values), persist);
Matthew Barth651f03a2020-08-27 16:15:11 -0500392}
393
Matthew Barthb584d812021-03-11 15:55:04 -0600394// Get a set property handler function for a configured value of the "Current"
Matthew Barth216229c2020-09-24 13:47:33 -0500395// property
Matthew Barthbc89a8a2021-05-25 15:42:58 -0500396std::function<void(DBusZone&, Zone&)> current(const json& jsonObj, bool persist)
Matthew Barth651f03a2020-08-27 16:15:11 -0500397{
Matthew Barth216229c2020-09-24 13:47:33 -0500398 // Use default value for "Current" property if no "value" entry given
399 if (!jsonObj.contains("value"))
400 {
Matthew Barthb584d812021-03-11 15:55:04 -0600401 log<level::INFO>("No 'value' found for \"Current\" property, "
402 "using default",
403 entry("JSON=%s", jsonObj.dump().c_str()));
404 // Set persist state of property
Matthew Barthbc89a8a2021-05-25 15:42:58 -0500405 return Zone::setPropertyPersist(DBusZone::thermalModeIntf,
406 DBusZone::currentProp, persist);
Matthew Barth216229c2020-09-24 13:47:33 -0500407 }
408
Matthew Barthb584d812021-03-11 15:55:04 -0600409 return Zone::setProperty<std::string>(
Matthew Barthbc89a8a2021-05-25 15:42:58 -0500410 DBusZone::thermalModeIntf, DBusZone::currentProp, &DBusZone::current,
Matthew Barthb584d812021-03-11 15:55:04 -0600411 jsonObj["value"].get<std::string>(), persist);
Matthew Barth651f03a2020-08-27 16:15:11 -0500412}
Matthew Barthb584d812021-03-11 15:55:04 -0600413
Matthew Barth651f03a2020-08-27 16:15:11 -0500414} // namespace zone::property
415
Matthew Barth4f0d3b72020-08-27 14:32:15 -0500416} // namespace phosphor::fan::control::json