blob: 6701c834e9e0350010f928e388c5f3256daa56d2 [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 Barthab8e4b82021-05-27 13:25:46 -050051 ConfigBase(jsonObj), _dbusZone{}, _manager(mgr), _defaultFloor(0),
Matthew Barth2504c772021-05-27 14:02:31 -050052 _incDelay(0), _decInterval(0), _floor(0), _target(0), _incDelta(0),
53 _decDelta(0), _requestTargetBase(0), _isActive(true),
Matthew Barthab8e4b82021-05-27 13:25:46 -050054 _incTimer(event, std::bind(&Zone::incTimerExpired, this)),
Matthew Barth007de092021-03-25 13:56:04 -050055 _decTimer(event, std::bind(&Zone::decTimerExpired, this))
Matthew Barth4f0d3b72020-08-27 14:32:15 -050056{
Matthew Barthe47c9582021-03-09 14:24:02 -060057 // Increase delay is optional, defaults to 0
Matthew Barth4f0d3b72020-08-27 14:32:15 -050058 if (jsonObj.contains("increase_delay"))
59 {
Matthew Barth007de092021-03-25 13:56:04 -050060 _incDelay =
61 std::chrono::seconds(jsonObj["increase_delay"].get<uint64_t>());
Matthew Barth4f0d3b72020-08-27 14:32:15 -050062 }
Matthew Barthab8e4b82021-05-27 13:25:46 -050063
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
Matthew Barth2504c772021-05-27 14:02:31 -050084 // 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 }
Matthew Barthab8e4b82021-05-27 13:25:46 -050091
Matthew Barth651f03a2020-08-27 16:15:11 -050092 // Setting properties on interfaces to be served are optional
93 if (jsonObj.contains("interfaces"))
94 {
95 setInterfaces(jsonObj);
96 }
Matthew Barth14303a42021-05-21 13:04:08 -050097}
Matthew Barth007de092021-03-25 13:56:04 -050098
Matthew Barth14303a42021-05-21 13:04:08 -050099void Zone::enable()
100{
Matthew Barthbc89a8a2021-05-25 15:42:58 -0500101 // Create thermal control dbus object
102 _dbusZone = std::make_unique<DBusZone>(*this);
Matthew Bartha4483742021-03-25 14:09:01 -0500103
Matthew Barthbc89a8a2021-05-25 15:42:58 -0500104 // 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();
Matthew Bartha4483742021-03-25 14:09:01 -0500120
Matthew Barth2504c772021-05-27 14:02:31 -0500121 // 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 }
Matthew Barth4f0d3b72020-08-27 14:32:15 -0500127}
128
Matthew Barthde90fb42021-03-04 16:34:28 -0600129void Zone::addFan(std::unique_ptr<Fan> fan)
130{
131 _fans.emplace_back(std::move(fan));
132}
133
Matthew Barth8ba715e2021-03-05 09:00:05 -0600134void Zone::setTarget(uint64_t target)
135{
Matt Spinler5a2b5012021-07-22 14:13:19 -0600136 if (_isActive)
Matthew Barth8ba715e2021-03-05 09:00:05 -0600137 {
138 _target = target;
139 for (auto& fan : _fans)
140 {
141 fan->setTarget(_target);
142 }
143 }
144}
145
Matthew Barth53680112021-09-23 11:20:41 -0500146void Zone::setTargetHold(const std::string& ident, uint64_t target, bool hold)
147{
148 if (!hold)
149 {
150 _holds.erase(ident);
151 }
152 else
153 {
154 _holds[ident] = target;
155 _isActive = false;
156 }
157
158 auto itHoldMax = std::max_element(_holds.begin(), _holds.end(),
159 [](const auto& aHold, const auto& bHold) {
160 return aHold.second < bHold.second;
161 });
162 if (itHoldMax == _holds.end())
163 {
164 _isActive = true;
165 }
166 else
167 {
168 _target = itHoldMax->second;
169 for (auto& fan : _fans)
170 {
171 fan->setTarget(_target);
172 }
173 }
174}
175
Matthew Barth12cb1252021-03-08 16:47:30 -0600176void Zone::setFloor(uint64_t target)
177{
178 // Check all entries are set to allow floor to be set
Matthew Barth8ba715e2021-03-05 09:00:05 -0600179 auto pred = [](const auto& entry) { return entry.second; };
Matthew Barth12cb1252021-03-08 16:47:30 -0600180 if (std::all_of(_floorChange.begin(), _floorChange.end(), pred))
181 {
182 _floor = target;
183 // Floor above target, update target to floor
184 if (_target < _floor)
185 {
186 requestIncrease(_floor - _target);
187 }
188 }
189}
190
191void Zone::requestIncrease(uint64_t targetDelta)
192{
Matthew Barth2b3253e2021-03-09 14:51:16 -0600193 // Only increase when delta is higher than the current increase delta for
194 // the zone and currently under ceiling
195 if (targetDelta > _incDelta && _target < _ceiling)
196 {
197 auto requestTarget = getRequestTargetBase();
198 requestTarget = (targetDelta - _incDelta) + requestTarget;
199 _incDelta = targetDelta;
200 // Target can not go above a current ceiling
201 if (requestTarget > _ceiling)
202 {
203 requestTarget = _ceiling;
204 }
Matthew Barth8ba715e2021-03-05 09:00:05 -0600205 setTarget(requestTarget);
Matthew Barth007de092021-03-25 13:56:04 -0500206 // Restart timer countdown for fan target increase
207 _incTimer.restartOnce(_incDelay);
Matthew Barth2b3253e2021-03-09 14:51:16 -0600208 }
Matthew Barth12cb1252021-03-08 16:47:30 -0600209}
210
Matthew Barth007de092021-03-25 13:56:04 -0500211void Zone::incTimerExpired()
212{
213 // Clear increase delta when timer expires allowing additional target
214 // increase requests or target decreases to occur
215 _incDelta = 0;
216}
217
Matthew Barth45c44ea2021-03-03 13:16:14 -0600218void Zone::requestDecrease(uint64_t targetDelta)
219{
220 // Only decrease the lowest target delta requested
221 if (_decDelta == 0 || targetDelta < _decDelta)
222 {
223 _decDelta = targetDelta;
224 }
225}
226
Matthew Barth007de092021-03-25 13:56:04 -0500227void Zone::decTimerExpired()
228{
229 // Check all entries are set to allow a decrease
230 auto pred = [](auto const& entry) { return entry.second; };
231 auto decAllowed = std::all_of(_decAllowed.begin(), _decAllowed.end(), pred);
232
233 // Only decrease targets when allowed, a requested decrease target delta
234 // exists, where no requested increases exist and the increase timer is not
235 // running (i.e. not in the middle of increasing)
236 if (decAllowed && _decDelta != 0 && _incDelta == 0 &&
237 !_incTimer.isEnabled())
238 {
239 auto requestTarget = getRequestTargetBase();
240 // Request target should not start above ceiling
241 if (requestTarget > _ceiling)
242 {
243 requestTarget = _ceiling;
244 }
245 // Target can not go below the defined floor
246 if ((requestTarget < _decDelta) || (requestTarget - _decDelta < _floor))
247 {
248 requestTarget = _floor;
249 }
250 else
251 {
252 requestTarget = requestTarget - _decDelta;
253 }
254 setTarget(requestTarget);
255 }
256 // Clear decrease delta when timer expires
257 _decDelta = 0;
258 // Decrease timer is restarted since its repeating
259}
260
Matthew Bartha0dd1352021-03-09 11:10:49 -0600261void Zone::setPersisted(const std::string& intf, const std::string& prop)
262{
263 if (std::find_if(_propsPersisted[intf].begin(), _propsPersisted[intf].end(),
Matthew Barthbc89a8a2021-05-25 15:42:58 -0500264 [&prop](const auto& p) { return prop == p; }) ==
Matthew Bartha0dd1352021-03-09 11:10:49 -0600265 _propsPersisted[intf].end())
266 {
267 _propsPersisted[intf].emplace_back(prop);
268 }
269}
270
Matthew Barth279183f2021-05-25 10:19:43 -0500271bool Zone::isPersisted(const std::string& intf, const std::string& prop) const
272{
273 auto it = _propsPersisted.find(intf);
274 if (it == _propsPersisted.end())
275 {
276 return false;
277 }
278
279 return std::any_of(it->second.begin(), it->second.end(),
280 [&prop](const auto& p) { return prop == p; });
281}
282
Matthew Barthab8e4b82021-05-27 13:25:46 -0500283void Zone::setPowerOnTarget(const json& jsonObj)
Matthew Barth4f0d3b72020-08-27 14:32:15 -0500284{
Matthew Barth12e888f2021-08-20 11:41:32 -0500285 if (!jsonObj.contains("poweron_target"))
Matthew Barth4f0d3b72020-08-27 14:32:15 -0500286 {
Matthew Barthab8e4b82021-05-27 13:25:46 -0500287 auto msg = "Missing required zone's poweron target";
288 log<level::ERR>(msg, entry("JSON=%s", jsonObj.dump().c_str()));
289 throw std::runtime_error(msg);
Matthew Barth4f0d3b72020-08-27 14:32:15 -0500290 }
Matthew Barth12e888f2021-08-20 11:41:32 -0500291 _poweronTarget = jsonObj["poweron_target"].get<uint64_t>();
Matthew Barth4f0d3b72020-08-27 14:32:15 -0500292}
293
Matthew Barth651f03a2020-08-27 16:15:11 -0500294void Zone::setInterfaces(const json& jsonObj)
295{
296 for (const auto& interface : jsonObj["interfaces"])
297 {
298 if (!interface.contains("name") || !interface.contains("properties"))
299 {
300 log<level::ERR>("Missing required zone interface attributes",
301 entry("JSON=%s", interface.dump().c_str()));
302 throw std::runtime_error(
303 "Missing required zone interface attributes");
304 }
Matthew Barth216229c2020-09-24 13:47:33 -0500305 auto propFuncs =
306 _intfPropHandlers.find(interface["name"].get<std::string>());
307 if (propFuncs == _intfPropHandlers.end())
308 {
309 // Construct list of available configurable interfaces
310 auto intfs = std::accumulate(
311 std::next(_intfPropHandlers.begin()), _intfPropHandlers.end(),
312 _intfPropHandlers.begin()->first, [](auto list, auto intf) {
313 return std::move(list) + ", " + intf.first;
314 });
315 log<level::ERR>("Configured interface not available",
316 entry("JSON=%s", interface.dump().c_str()),
317 entry("AVAILABLE_INTFS=%s", intfs.c_str()));
318 throw std::runtime_error("Configured interface not available");
319 }
320
Matthew Barth651f03a2020-08-27 16:15:11 -0500321 for (const auto& property : interface["properties"])
322 {
323 if (!property.contains("name"))
324 {
325 log<level::ERR>(
326 "Missing required interface property attributes",
327 entry("JSON=%s", property.dump().c_str()));
328 throw std::runtime_error(
329 "Missing required interface property attributes");
330 }
331 // Attribute "persist" is optional, defaults to `false`
332 auto persist = false;
333 if (property.contains("persist"))
334 {
335 persist = property["persist"].get<bool>();
336 }
337 // Property name from JSON must exactly match supported
338 // index names to functions in property namespace
Matthew Barth216229c2020-09-24 13:47:33 -0500339 auto propFunc =
340 propFuncs->second.find(property["name"].get<std::string>());
341 if (propFunc == propFuncs->second.end())
Matthew Barth651f03a2020-08-27 16:15:11 -0500342 {
Matthew Barth216229c2020-09-24 13:47:33 -0500343 // Construct list of available configurable properties
Matthew Barth651f03a2020-08-27 16:15:11 -0500344 auto props = std::accumulate(
Matthew Barth216229c2020-09-24 13:47:33 -0500345 std::next(propFuncs->second.begin()),
346 propFuncs->second.end(), propFuncs->second.begin()->first,
347 [](auto list, auto prop) {
Matthew Barth651f03a2020-08-27 16:15:11 -0500348 return std::move(list) + ", " + prop.first;
349 });
Matthew Barth216229c2020-09-24 13:47:33 -0500350 log<level::ERR>("Configured property not available",
Matthew Barth651f03a2020-08-27 16:15:11 -0500351 entry("JSON=%s", property.dump().c_str()),
352 entry("AVAILABLE_PROPS=%s", props.c_str()));
353 throw std::runtime_error(
354 "Configured property function not available");
355 }
Matthew Barthbc89a8a2021-05-25 15:42:58 -0500356
357 _propInitFunctions.emplace_back(
358 propFunc->second(property, persist));
Matthew Barth651f03a2020-08-27 16:15:11 -0500359 }
Matthew Barth651f03a2020-08-27 16:15:11 -0500360 }
361}
362
Matt Spinler9db6dd12021-10-29 16:10:08 -0500363json Zone::dump() const
364{
365 json output;
366
367 output["active"] = _isActive;
368 output["floor"] = _floor;
369 output["target"] = _target;
370 output["increase_delta"] = _incDelta;
371 output["decrease_delta"] = _decDelta;
372 output["power_on_target"] = _poweronTarget;
373 output["default_ceiling"] = _defaultCeiling;
374 output["default_floor"] = _defaultFloor;
375 output["increase_delay"] = _incDelay.count();
376 output["decrease_interval"] = _decInterval.count();
377 output["requested_target_base"] = _requestTargetBase;
378 output["floor_change"] = _floorChange;
379 output["decrease_allowed"] = _decAllowed;
380 output["persisted_props"] = _propsPersisted;
381 output["holds"] = _holds;
382
383 return output;
384}
385
Matthew Barth651f03a2020-08-27 16:15:11 -0500386/**
Matthew Barth216229c2020-09-24 13:47:33 -0500387 * Properties of interfaces supported by the zone configuration that return
Matthew Barthab8e4b82021-05-27 13:25:46 -0500388 * a handler function that sets the zone's property value(s) and persist
389 * state.
Matthew Barth651f03a2020-08-27 16:15:11 -0500390 */
391namespace zone::property
392{
Matthew Barthb584d812021-03-11 15:55:04 -0600393// Get a set property handler function for the configured values of the
394// "Supported" property
Matthew Barthbc89a8a2021-05-25 15:42:58 -0500395std::function<void(DBusZone&, Zone&)> supported(const json& jsonObj,
396 bool persist)
Matthew Barth651f03a2020-08-27 16:15:11 -0500397{
398 std::vector<std::string> values;
Matthew Barth216229c2020-09-24 13:47:33 -0500399 if (!jsonObj.contains("values"))
Matthew Barth651f03a2020-08-27 16:15:11 -0500400 {
Matthew Barthab8e4b82021-05-27 13:25:46 -0500401 log<level::ERR>("No 'values' found for \"Supported\" property, "
402 "using an empty list",
403 entry("JSON=%s", jsonObj.dump().c_str()));
Matthew Barth216229c2020-09-24 13:47:33 -0500404 }
405 else
406 {
407 for (const auto& value : jsonObj["values"])
408 {
409 if (!value.contains("value"))
410 {
411 log<level::ERR>("No 'value' found for \"Supported\" property "
412 "entry, skipping",
413 entry("JSON=%s", value.dump().c_str()));
414 }
415 else
416 {
417 values.emplace_back(value["value"].get<std::string>());
418 }
419 }
Matthew Barth651f03a2020-08-27 16:15:11 -0500420 }
421
Matthew Barthb584d812021-03-11 15:55:04 -0600422 return Zone::setProperty<std::vector<std::string>>(
Matthew Barthbc89a8a2021-05-25 15:42:58 -0500423 DBusZone::thermalModeIntf, DBusZone::supportedProp,
424 &DBusZone::supported, std::move(values), persist);
Matthew Barth651f03a2020-08-27 16:15:11 -0500425}
426
Matthew Barthab8e4b82021-05-27 13:25:46 -0500427// Get a set property handler function for a configured value of the
428// "Current" property
Matthew Barthbc89a8a2021-05-25 15:42:58 -0500429std::function<void(DBusZone&, Zone&)> current(const json& jsonObj, bool persist)
Matthew Barth651f03a2020-08-27 16:15:11 -0500430{
Matthew Barth216229c2020-09-24 13:47:33 -0500431 // Use default value for "Current" property if no "value" entry given
432 if (!jsonObj.contains("value"))
433 {
Matthew Barthb584d812021-03-11 15:55:04 -0600434 log<level::INFO>("No 'value' found for \"Current\" property, "
435 "using default",
436 entry("JSON=%s", jsonObj.dump().c_str()));
437 // Set persist state of property
Matthew Barthbc89a8a2021-05-25 15:42:58 -0500438 return Zone::setPropertyPersist(DBusZone::thermalModeIntf,
439 DBusZone::currentProp, persist);
Matthew Barth216229c2020-09-24 13:47:33 -0500440 }
441
Matthew Barthb584d812021-03-11 15:55:04 -0600442 return Zone::setProperty<std::string>(
Matthew Barthbc89a8a2021-05-25 15:42:58 -0500443 DBusZone::thermalModeIntf, DBusZone::currentProp, &DBusZone::current,
Matthew Barthb584d812021-03-11 15:55:04 -0600444 jsonObj["value"].get<std::string>(), persist);
Matthew Barth651f03a2020-08-27 16:15:11 -0500445}
Matthew Barthb584d812021-03-11 15:55:04 -0600446
Matthew Barth651f03a2020-08-27 16:15:11 -0500447} // namespace zone::property
448
Matthew Barth4f0d3b72020-08-27 14:32:15 -0500449} // namespace phosphor::fan::control::json