blob: 6cda0a97a638fc6203b17d40f2652166e3f27e57 [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
Matt Spinler40554d82021-10-26 15:23:59 -050018#include "../utils/flight_recorder.hpp"
Matthew Barthbc89a8a2021-05-25 15:42:58 -050019#include "dbus_zone.hpp"
Matthew Barthde90fb42021-03-04 16:34:28 -060020#include "fan.hpp"
Matthew Barth9403a212021-05-17 09:31:50 -050021#include "sdbusplus.hpp"
Matthew Barth216229c2020-09-24 13:47:33 -050022
Matthew Barth4f0d3b72020-08-27 14:32:15 -050023#include <nlohmann/json.hpp>
24#include <phosphor-logging/log.hpp>
Matthew Barth603ef162021-03-24 15:34:53 -050025#include <sdeventplus/event.hpp>
Matthew Barth4f0d3b72020-08-27 14:32:15 -050026
Matthew Bartha0dd1352021-03-09 11:10:49 -060027#include <algorithm>
Matthew Barth007de092021-03-25 13:56:04 -050028#include <chrono>
Matthew Barth651f03a2020-08-27 16:15:11 -050029#include <iterator>
30#include <map>
Matthew Barthbc89a8a2021-05-25 15:42:58 -050031#include <memory>
Matthew Barth651f03a2020-08-27 16:15:11 -050032#include <numeric>
Matthew Barth651f03a2020-08-27 16:15:11 -050033#include <utility>
Matthew Barth216229c2020-09-24 13:47:33 -050034#include <vector>
Matthew Barth651f03a2020-08-27 16:15:11 -050035
Matthew Barth4f0d3b72020-08-27 14:32:15 -050036namespace phosphor::fan::control::json
37{
38
39using json = nlohmann::json;
40using namespace phosphor::logging;
41
Matthew Barthbc89a8a2021-05-25 15:42:58 -050042const 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}}}};
Matthew Barth651f03a2020-08-27 16:15:11 -050050
Matthew Barth9403a212021-05-17 09:31:50 -050051Zone::Zone(const json& jsonObj, const sdeventplus::Event& event, Manager* mgr) :
Matthew Barthab8e4b82021-05-27 13:25:46 -050052 ConfigBase(jsonObj), _dbusZone{}, _manager(mgr), _defaultFloor(0),
Matthew Barth2504c772021-05-27 14:02:31 -050053 _incDelay(0), _decInterval(0), _floor(0), _target(0), _incDelta(0),
54 _decDelta(0), _requestTargetBase(0), _isActive(true),
Matthew Barthab8e4b82021-05-27 13:25:46 -050055 _incTimer(event, std::bind(&Zone::incTimerExpired, this)),
Matthew Barth007de092021-03-25 13:56:04 -050056 _decTimer(event, std::bind(&Zone::decTimerExpired, this))
Matthew Barth4f0d3b72020-08-27 14:32:15 -050057{
Matthew Barthe47c9582021-03-09 14:24:02 -060058 // Increase delay is optional, defaults to 0
Matthew Barth4f0d3b72020-08-27 14:32:15 -050059 if (jsonObj.contains("increase_delay"))
60 {
Matthew Barth007de092021-03-25 13:56:04 -050061 _incDelay =
62 std::chrono::seconds(jsonObj["increase_delay"].get<uint64_t>());
Matthew Barth4f0d3b72020-08-27 14:32:15 -050063 }
Matthew Barthab8e4b82021-05-27 13:25:46 -050064
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
Matthew Barth2504c772021-05-27 14:02:31 -050085 // 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 }
Matthew Barthab8e4b82021-05-27 13:25:46 -050092
Matthew Barth651f03a2020-08-27 16:15:11 -050093 // Setting properties on interfaces to be served are optional
94 if (jsonObj.contains("interfaces"))
95 {
96 setInterfaces(jsonObj);
97 }
Matthew Barth14303a42021-05-21 13:04:08 -050098}
Matthew Barth007de092021-03-25 13:56:04 -050099
Matthew Barth14303a42021-05-21 13:04:08 -0500100void Zone::enable()
101{
Matthew Barthbc89a8a2021-05-25 15:42:58 -0500102 // Create thermal control dbus object
103 _dbusZone = std::make_unique<DBusZone>(*this);
Matthew Bartha4483742021-03-25 14:09:01 -0500104
Matthew Barthbc89a8a2021-05-25 15:42:58 -0500105 // 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();
Matthew Bartha4483742021-03-25 14:09:01 -0500121
Matthew Barth2504c772021-05-27 14:02:31 -0500122 // 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 }
Matthew Barth4f0d3b72020-08-27 14:32:15 -0500128}
129
Matthew Barthde90fb42021-03-04 16:34:28 -0600130void Zone::addFan(std::unique_ptr<Fan> fan)
131{
132 _fans.emplace_back(std::move(fan));
133}
134
Matthew Barth8ba715e2021-03-05 09:00:05 -0600135void Zone::setTarget(uint64_t target)
136{
Matt Spinler5a2b5012021-07-22 14:13:19 -0600137 if (_isActive)
Matthew Barth8ba715e2021-03-05 09:00:05 -0600138 {
139 _target = target;
140 for (auto& fan : _fans)
141 {
142 fan->setTarget(_target);
143 }
144 }
145}
146
Matthew Barth53680112021-09-23 11:20:41 -0500147void Zone::setTargetHold(const std::string& ident, uint64_t target, bool hold)
148{
149 if (!hold)
150 {
Matt Spinler40554d82021-10-26 15:23:59 -0500151 _targetHolds.erase(ident);
Matthew Barth53680112021-09-23 11:20:41 -0500152 }
153 else
154 {
Matt Spinler40554d82021-10-26 15:23:59 -0500155 _targetHolds[ident] = target;
Matthew Barth53680112021-09-23 11:20:41 -0500156 _isActive = false;
157 }
158
Matt Spinler40554d82021-10-26 15:23:59 -0500159 auto itHoldMax = std::max_element(_targetHolds.begin(), _targetHolds.end(),
Matthew Barth53680112021-09-23 11:20:41 -0500160 [](const auto& aHold, const auto& bHold) {
161 return aHold.second < bHold.second;
162 });
Matt Spinler40554d82021-10-26 15:23:59 -0500163 if (itHoldMax == _targetHolds.end())
Matthew Barth53680112021-09-23 11:20:41 -0500164 {
165 _isActive = true;
166 }
167 else
168 {
169 _target = itHoldMax->second;
170 for (auto& fan : _fans)
171 {
172 fan->setTarget(_target);
173 }
174 }
175}
176
Matt Spinler40554d82021-10-26 15:23:59 -0500177void Zone::setFloorHold(const std::string& ident, uint64_t target, bool hold)
178{
179 using namespace std::string_literals;
180
181 if (!hold)
182 {
183 size_t removed = _floorHolds.erase(ident);
184 if (removed)
185 {
186 FlightRecorder::instance().log(
187 "zone-floor"s + getName(),
188 fmt::format("{} is removing floor hold", ident));
189 }
190 }
191 else
192 {
193 if (!((_floorHolds.find(ident) != _floorHolds.end()) &&
194 (_floorHolds[ident] == target)))
195 {
196 FlightRecorder::instance().log(
197 "zone-floor"s + getName(),
198 fmt::format("{} is setting floor hold to {}", ident, target));
199 }
200 _floorHolds[ident] = target;
201 }
202
203 if (!std::all_of(_floorChange.begin(), _floorChange.end(),
204 [](const auto& entry) { return entry.second; }))
205 {
206 return;
207 }
208
209 auto itHoldMax = std::max_element(_floorHolds.begin(), _floorHolds.end(),
210 [](const auto& aHold, const auto& bHold) {
211 return aHold.second < bHold.second;
212 });
213 if (itHoldMax == _floorHolds.end())
214 {
215 if (_floor != _defaultFloor)
216 {
217 FlightRecorder::instance().log(
218 "zone-floor"s + getName(),
219 fmt::format("No set floor exists, using default floor",
220 _defaultFloor));
221 }
222 _floor = _defaultFloor;
223 }
224 else
225 {
226 if (_floor != itHoldMax->second)
227 {
228 FlightRecorder::instance().log(
229 "zone-floor"s + getName(),
230 fmt::format("Setting new floor to {}", itHoldMax->second));
231 }
232 _floor = itHoldMax->second;
233 }
234
235 // Floor above target, update target to floor
236 if (_target < _floor)
237 {
238 requestIncrease(_floor - _target);
239 }
240}
241
Matthew Barth12cb1252021-03-08 16:47:30 -0600242void Zone::setFloor(uint64_t target)
243{
244 // Check all entries are set to allow floor to be set
Matthew Barth8ba715e2021-03-05 09:00:05 -0600245 auto pred = [](const auto& entry) { return entry.second; };
Matthew Barth12cb1252021-03-08 16:47:30 -0600246 if (std::all_of(_floorChange.begin(), _floorChange.end(), pred))
247 {
248 _floor = target;
249 // Floor above target, update target to floor
250 if (_target < _floor)
251 {
252 requestIncrease(_floor - _target);
253 }
254 }
255}
256
257void Zone::requestIncrease(uint64_t targetDelta)
258{
Matthew Barth2b3253e2021-03-09 14:51:16 -0600259 // Only increase when delta is higher than the current increase delta for
260 // the zone and currently under ceiling
261 if (targetDelta > _incDelta && _target < _ceiling)
262 {
263 auto requestTarget = getRequestTargetBase();
264 requestTarget = (targetDelta - _incDelta) + requestTarget;
265 _incDelta = targetDelta;
266 // Target can not go above a current ceiling
267 if (requestTarget > _ceiling)
268 {
269 requestTarget = _ceiling;
270 }
Matthew Barth8ba715e2021-03-05 09:00:05 -0600271 setTarget(requestTarget);
Matthew Barth007de092021-03-25 13:56:04 -0500272 // Restart timer countdown for fan target increase
273 _incTimer.restartOnce(_incDelay);
Matthew Barth2b3253e2021-03-09 14:51:16 -0600274 }
Matthew Barth12cb1252021-03-08 16:47:30 -0600275}
276
Matthew Barth007de092021-03-25 13:56:04 -0500277void Zone::incTimerExpired()
278{
279 // Clear increase delta when timer expires allowing additional target
280 // increase requests or target decreases to occur
281 _incDelta = 0;
282}
283
Matthew Barth45c44ea2021-03-03 13:16:14 -0600284void Zone::requestDecrease(uint64_t targetDelta)
285{
286 // Only decrease the lowest target delta requested
287 if (_decDelta == 0 || targetDelta < _decDelta)
288 {
289 _decDelta = targetDelta;
290 }
291}
292
Matthew Barth007de092021-03-25 13:56:04 -0500293void Zone::decTimerExpired()
294{
295 // Check all entries are set to allow a decrease
296 auto pred = [](auto const& entry) { return entry.second; };
297 auto decAllowed = std::all_of(_decAllowed.begin(), _decAllowed.end(), pred);
298
299 // Only decrease targets when allowed, a requested decrease target delta
300 // exists, where no requested increases exist and the increase timer is not
301 // running (i.e. not in the middle of increasing)
302 if (decAllowed && _decDelta != 0 && _incDelta == 0 &&
303 !_incTimer.isEnabled())
304 {
305 auto requestTarget = getRequestTargetBase();
306 // Request target should not start above ceiling
307 if (requestTarget > _ceiling)
308 {
309 requestTarget = _ceiling;
310 }
311 // Target can not go below the defined floor
312 if ((requestTarget < _decDelta) || (requestTarget - _decDelta < _floor))
313 {
314 requestTarget = _floor;
315 }
316 else
317 {
318 requestTarget = requestTarget - _decDelta;
319 }
320 setTarget(requestTarget);
321 }
322 // Clear decrease delta when timer expires
323 _decDelta = 0;
324 // Decrease timer is restarted since its repeating
325}
326
Matthew Bartha0dd1352021-03-09 11:10:49 -0600327void Zone::setPersisted(const std::string& intf, const std::string& prop)
328{
329 if (std::find_if(_propsPersisted[intf].begin(), _propsPersisted[intf].end(),
Matthew Barthbc89a8a2021-05-25 15:42:58 -0500330 [&prop](const auto& p) { return prop == p; }) ==
Matthew Bartha0dd1352021-03-09 11:10:49 -0600331 _propsPersisted[intf].end())
332 {
333 _propsPersisted[intf].emplace_back(prop);
334 }
335}
336
Matthew Barth279183f2021-05-25 10:19:43 -0500337bool Zone::isPersisted(const std::string& intf, const std::string& prop) const
338{
339 auto it = _propsPersisted.find(intf);
340 if (it == _propsPersisted.end())
341 {
342 return false;
343 }
344
345 return std::any_of(it->second.begin(), it->second.end(),
346 [&prop](const auto& p) { return prop == p; });
347}
348
Matthew Barthab8e4b82021-05-27 13:25:46 -0500349void Zone::setPowerOnTarget(const json& jsonObj)
Matthew Barth4f0d3b72020-08-27 14:32:15 -0500350{
Matthew Barth12e888f2021-08-20 11:41:32 -0500351 if (!jsonObj.contains("poweron_target"))
Matthew Barth4f0d3b72020-08-27 14:32:15 -0500352 {
Matthew Barthab8e4b82021-05-27 13:25:46 -0500353 auto msg = "Missing required zone's poweron target";
354 log<level::ERR>(msg, entry("JSON=%s", jsonObj.dump().c_str()));
355 throw std::runtime_error(msg);
Matthew Barth4f0d3b72020-08-27 14:32:15 -0500356 }
Matthew Barth12e888f2021-08-20 11:41:32 -0500357 _poweronTarget = jsonObj["poweron_target"].get<uint64_t>();
Matthew Barth4f0d3b72020-08-27 14:32:15 -0500358}
359
Matthew Barth651f03a2020-08-27 16:15:11 -0500360void Zone::setInterfaces(const json& jsonObj)
361{
362 for (const auto& interface : jsonObj["interfaces"])
363 {
364 if (!interface.contains("name") || !interface.contains("properties"))
365 {
366 log<level::ERR>("Missing required zone interface attributes",
367 entry("JSON=%s", interface.dump().c_str()));
368 throw std::runtime_error(
369 "Missing required zone interface attributes");
370 }
Matthew Barth216229c2020-09-24 13:47:33 -0500371 auto propFuncs =
372 _intfPropHandlers.find(interface["name"].get<std::string>());
373 if (propFuncs == _intfPropHandlers.end())
374 {
375 // Construct list of available configurable interfaces
376 auto intfs = std::accumulate(
377 std::next(_intfPropHandlers.begin()), _intfPropHandlers.end(),
378 _intfPropHandlers.begin()->first, [](auto list, auto intf) {
379 return std::move(list) + ", " + intf.first;
380 });
381 log<level::ERR>("Configured interface not available",
382 entry("JSON=%s", interface.dump().c_str()),
383 entry("AVAILABLE_INTFS=%s", intfs.c_str()));
384 throw std::runtime_error("Configured interface not available");
385 }
386
Matthew Barth651f03a2020-08-27 16:15:11 -0500387 for (const auto& property : interface["properties"])
388 {
389 if (!property.contains("name"))
390 {
391 log<level::ERR>(
392 "Missing required interface property attributes",
393 entry("JSON=%s", property.dump().c_str()));
394 throw std::runtime_error(
395 "Missing required interface property attributes");
396 }
397 // Attribute "persist" is optional, defaults to `false`
398 auto persist = false;
399 if (property.contains("persist"))
400 {
401 persist = property["persist"].get<bool>();
402 }
403 // Property name from JSON must exactly match supported
404 // index names to functions in property namespace
Matthew Barth216229c2020-09-24 13:47:33 -0500405 auto propFunc =
406 propFuncs->second.find(property["name"].get<std::string>());
407 if (propFunc == propFuncs->second.end())
Matthew Barth651f03a2020-08-27 16:15:11 -0500408 {
Matthew Barth216229c2020-09-24 13:47:33 -0500409 // Construct list of available configurable properties
Matthew Barth651f03a2020-08-27 16:15:11 -0500410 auto props = std::accumulate(
Matthew Barth216229c2020-09-24 13:47:33 -0500411 std::next(propFuncs->second.begin()),
412 propFuncs->second.end(), propFuncs->second.begin()->first,
413 [](auto list, auto prop) {
Matthew Barth651f03a2020-08-27 16:15:11 -0500414 return std::move(list) + ", " + prop.first;
415 });
Matthew Barth216229c2020-09-24 13:47:33 -0500416 log<level::ERR>("Configured property not available",
Matthew Barth651f03a2020-08-27 16:15:11 -0500417 entry("JSON=%s", property.dump().c_str()),
418 entry("AVAILABLE_PROPS=%s", props.c_str()));
419 throw std::runtime_error(
420 "Configured property function not available");
421 }
Matthew Barthbc89a8a2021-05-25 15:42:58 -0500422
423 _propInitFunctions.emplace_back(
424 propFunc->second(property, persist));
Matthew Barth651f03a2020-08-27 16:15:11 -0500425 }
Matthew Barth651f03a2020-08-27 16:15:11 -0500426 }
427}
428
Matt Spinler9db6dd12021-10-29 16:10:08 -0500429json Zone::dump() const
430{
431 json output;
432
433 output["active"] = _isActive;
434 output["floor"] = _floor;
Matthew Barth077448a2021-12-02 20:38:15 -0600435 output["ceiling"] = _ceiling;
Matt Spinler9db6dd12021-10-29 16:10:08 -0500436 output["target"] = _target;
437 output["increase_delta"] = _incDelta;
438 output["decrease_delta"] = _decDelta;
439 output["power_on_target"] = _poweronTarget;
440 output["default_ceiling"] = _defaultCeiling;
441 output["default_floor"] = _defaultFloor;
442 output["increase_delay"] = _incDelay.count();
443 output["decrease_interval"] = _decInterval.count();
444 output["requested_target_base"] = _requestTargetBase;
445 output["floor_change"] = _floorChange;
446 output["decrease_allowed"] = _decAllowed;
447 output["persisted_props"] = _propsPersisted;
Matt Spinler40554d82021-10-26 15:23:59 -0500448 output["target_holds"] = _targetHolds;
449 output["floor_holds"] = _floorHolds;
Matt Spinler9db6dd12021-10-29 16:10:08 -0500450
451 return output;
452}
453
Matthew Barth651f03a2020-08-27 16:15:11 -0500454/**
Matthew Barth216229c2020-09-24 13:47:33 -0500455 * Properties of interfaces supported by the zone configuration that return
Matthew Barthab8e4b82021-05-27 13:25:46 -0500456 * a handler function that sets the zone's property value(s) and persist
457 * state.
Matthew Barth651f03a2020-08-27 16:15:11 -0500458 */
459namespace zone::property
460{
Matthew Barthb584d812021-03-11 15:55:04 -0600461// Get a set property handler function for the configured values of the
462// "Supported" property
Matthew Barthbc89a8a2021-05-25 15:42:58 -0500463std::function<void(DBusZone&, Zone&)> supported(const json& jsonObj,
464 bool persist)
Matthew Barth651f03a2020-08-27 16:15:11 -0500465{
466 std::vector<std::string> values;
Matthew Barth216229c2020-09-24 13:47:33 -0500467 if (!jsonObj.contains("values"))
Matthew Barth651f03a2020-08-27 16:15:11 -0500468 {
Matthew Barthab8e4b82021-05-27 13:25:46 -0500469 log<level::ERR>("No 'values' found for \"Supported\" property, "
470 "using an empty list",
471 entry("JSON=%s", jsonObj.dump().c_str()));
Matthew Barth216229c2020-09-24 13:47:33 -0500472 }
473 else
474 {
475 for (const auto& value : jsonObj["values"])
476 {
477 if (!value.contains("value"))
478 {
479 log<level::ERR>("No 'value' found for \"Supported\" property "
480 "entry, skipping",
481 entry("JSON=%s", value.dump().c_str()));
482 }
483 else
484 {
485 values.emplace_back(value["value"].get<std::string>());
486 }
487 }
Matthew Barth651f03a2020-08-27 16:15:11 -0500488 }
489
Matthew Barthb584d812021-03-11 15:55:04 -0600490 return Zone::setProperty<std::vector<std::string>>(
Matthew Barthbc89a8a2021-05-25 15:42:58 -0500491 DBusZone::thermalModeIntf, DBusZone::supportedProp,
492 &DBusZone::supported, std::move(values), persist);
Matthew Barth651f03a2020-08-27 16:15:11 -0500493}
494
Matthew Barthab8e4b82021-05-27 13:25:46 -0500495// Get a set property handler function for a configured value of the
496// "Current" property
Matthew Barthbc89a8a2021-05-25 15:42:58 -0500497std::function<void(DBusZone&, Zone&)> current(const json& jsonObj, bool persist)
Matthew Barth651f03a2020-08-27 16:15:11 -0500498{
Matthew Barth216229c2020-09-24 13:47:33 -0500499 // Use default value for "Current" property if no "value" entry given
500 if (!jsonObj.contains("value"))
501 {
Matthew Barthb584d812021-03-11 15:55:04 -0600502 log<level::INFO>("No 'value' found for \"Current\" property, "
503 "using default",
504 entry("JSON=%s", jsonObj.dump().c_str()));
505 // Set persist state of property
Matthew Barthbc89a8a2021-05-25 15:42:58 -0500506 return Zone::setPropertyPersist(DBusZone::thermalModeIntf,
507 DBusZone::currentProp, persist);
Matthew Barth216229c2020-09-24 13:47:33 -0500508 }
509
Matthew Barthb584d812021-03-11 15:55:04 -0600510 return Zone::setProperty<std::string>(
Matthew Barthbc89a8a2021-05-25 15:42:58 -0500511 DBusZone::thermalModeIntf, DBusZone::currentProp, &DBusZone::current,
Matthew Barthb584d812021-03-11 15:55:04 -0600512 jsonObj["value"].get<std::string>(), persist);
Matthew Barth651f03a2020-08-27 16:15:11 -0500513}
Matthew Barthb584d812021-03-11 15:55:04 -0600514
Matthew Barth651f03a2020-08-27 16:15:11 -0500515} // namespace zone::property
516
Matthew Barth4f0d3b72020-08-27 14:32:15 -0500517} // namespace phosphor::fan::control::json