blob: d7d1a9043ab009be4e3066916f0c27e7625c6db0 [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 */
Matthew Bartha0dd1352021-03-09 11:10:49 -060016#include "config.h"
17
Matthew Barth4f0d3b72020-08-27 14:32:15 -050018#include "zone.hpp"
19
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 Bartha0dd1352021-03-09 11:10:49 -060023#include <cereal/archives/json.hpp>
24#include <cereal/cereal.hpp>
Matthew Barth4f0d3b72020-08-27 14:32:15 -050025#include <nlohmann/json.hpp>
26#include <phosphor-logging/log.hpp>
Matthew Barthacd737c2021-03-04 11:04:01 -060027#include <sdbusplus/bus.hpp>
Matthew Barth603ef162021-03-24 15:34:53 -050028#include <sdeventplus/event.hpp>
Matthew Barth4f0d3b72020-08-27 14:32:15 -050029
Matthew Bartha0dd1352021-03-09 11:10:49 -060030#include <algorithm>
Matthew Barth007de092021-03-25 13:56:04 -050031#include <chrono>
Matthew Bartha0dd1352021-03-09 11:10:49 -060032#include <filesystem>
33#include <fstream>
Matthew Barth651f03a2020-08-27 16:15:11 -050034#include <iterator>
35#include <map>
36#include <numeric>
Matthew Barth651f03a2020-08-27 16:15:11 -050037#include <utility>
Matthew Barth216229c2020-09-24 13:47:33 -050038#include <vector>
Matthew Barth651f03a2020-08-27 16:15:11 -050039
Matthew Barth4f0d3b72020-08-27 14:32:15 -050040namespace phosphor::fan::control::json
41{
42
43using json = nlohmann::json;
44using namespace phosphor::logging;
Matthew Bartha0dd1352021-03-09 11:10:49 -060045namespace fs = std::filesystem;
Matthew Barth4f0d3b72020-08-27 14:32:15 -050046
Matthew Barthb584d812021-03-11 15:55:04 -060047const std::map<std::string,
48 std::map<std::string, std::function<std::function<void(Zone*)>(
49 const json&, bool)>>>
Matthew Barth216229c2020-09-24 13:47:33 -050050 Zone::_intfPropHandlers = {{thermModeIntf,
51 {{supportedProp, zone::property::supported},
52 {currentProp, zone::property::current}}}};
Matthew Barth651f03a2020-08-27 16:15:11 -050053
Matthew Barth9403a212021-05-17 09:31:50 -050054Zone::Zone(const json& jsonObj, const sdeventplus::Event& event, Manager* mgr) :
Matthew Bartha0dd1352021-03-09 11:10:49 -060055 ConfigBase(jsonObj),
Matthew Barth9403a212021-05-17 09:31:50 -050056 ThermalObject(util::SDBusPlus::getBus(),
57 (fs::path{CONTROL_OBJPATH} /= getName()).c_str(), true),
Matthew Barth603ef162021-03-24 15:34:53 -050058 _manager(mgr), _incDelay(0), _floor(0), _target(0), _incDelta(0),
Matthew Barth007de092021-03-25 13:56:04 -050059 _decDelta(0), _requestTargetBase(0), _isActive(true),
60 _incTimer(event, std::bind(&Zone::incTimerExpired, this)),
61 _decTimer(event, std::bind(&Zone::decTimerExpired, this))
Matthew Barth4f0d3b72020-08-27 14:32:15 -050062{
Matthew Barthe47c9582021-03-09 14:24:02 -060063 // Increase delay is optional, defaults to 0
Matthew Barth4f0d3b72020-08-27 14:32:15 -050064 if (jsonObj.contains("increase_delay"))
65 {
Matthew Barth007de092021-03-25 13:56:04 -050066 _incDelay =
67 std::chrono::seconds(jsonObj["increase_delay"].get<uint64_t>());
Matthew Barth4f0d3b72020-08-27 14:32:15 -050068 }
Matthew Barthe47c9582021-03-09 14:24:02 -060069 setDefaultCeiling(jsonObj);
Matthew Barth4f0d3b72020-08-27 14:32:15 -050070 setDefaultFloor(jsonObj);
71 setDecInterval(jsonObj);
Matthew Barth651f03a2020-08-27 16:15:11 -050072 // Setting properties on interfaces to be served are optional
73 if (jsonObj.contains("interfaces"))
74 {
75 setInterfaces(jsonObj);
76 }
Matthew Barth14303a42021-05-21 13:04:08 -050077}
Matthew Barth007de092021-03-25 13:56:04 -050078
Matthew Barth14303a42021-05-21 13:04:08 -050079void Zone::enable()
80{
Matthew Bartha4483742021-03-25 14:09:01 -050081 // Restore thermal control current mode state
82 restoreCurrentMode();
83
84 // Emit object added for this zone dbus object
85 this->emit_object_added();
86
Matthew Barth007de092021-03-25 13:56:04 -050087 // Start timer for fan target decreases
88 _decTimer.restart(_decInterval);
Matthew Barth4f0d3b72020-08-27 14:32:15 -050089}
90
Matthew Barthde90fb42021-03-04 16:34:28 -060091void Zone::addFan(std::unique_ptr<Fan> fan)
92{
93 _fans.emplace_back(std::move(fan));
94}
95
Matthew Barth8ba715e2021-03-05 09:00:05 -060096void Zone::setTarget(uint64_t target)
97{
Matthew Barth6f787302021-03-25 15:01:01 -050098 if (_isActive && _target != target)
Matthew Barth8ba715e2021-03-05 09:00:05 -060099 {
100 _target = target;
101 for (auto& fan : _fans)
102 {
103 fan->setTarget(_target);
104 }
105 }
106}
107
108void Zone::setActiveAllow(const std::string& ident, bool isActiveAllow)
109{
110 _active[ident] = isActiveAllow;
111 if (!isActiveAllow)
112 {
113 _isActive = false;
114 }
115 else
116 {
117 // Check all entries are set to allow active fan control
118 auto actPred = [](const auto& entry) { return entry.second; };
119 _isActive = std::all_of(_active.begin(), _active.end(), actPred);
120 }
121}
122
Matthew Barth12cb1252021-03-08 16:47:30 -0600123void Zone::setFloor(uint64_t target)
124{
125 // Check all entries are set to allow floor to be set
Matthew Barth8ba715e2021-03-05 09:00:05 -0600126 auto pred = [](const auto& entry) { return entry.second; };
Matthew Barth12cb1252021-03-08 16:47:30 -0600127 if (std::all_of(_floorChange.begin(), _floorChange.end(), pred))
128 {
129 _floor = target;
130 // Floor above target, update target to floor
131 if (_target < _floor)
132 {
133 requestIncrease(_floor - _target);
134 }
135 }
136}
137
138void Zone::requestIncrease(uint64_t targetDelta)
139{
Matthew Barth2b3253e2021-03-09 14:51:16 -0600140 // Only increase when delta is higher than the current increase delta for
141 // the zone and currently under ceiling
142 if (targetDelta > _incDelta && _target < _ceiling)
143 {
144 auto requestTarget = getRequestTargetBase();
145 requestTarget = (targetDelta - _incDelta) + requestTarget;
146 _incDelta = targetDelta;
147 // Target can not go above a current ceiling
148 if (requestTarget > _ceiling)
149 {
150 requestTarget = _ceiling;
151 }
Matthew Barth8ba715e2021-03-05 09:00:05 -0600152 setTarget(requestTarget);
Matthew Barth007de092021-03-25 13:56:04 -0500153 // Restart timer countdown for fan target increase
154 _incTimer.restartOnce(_incDelay);
Matthew Barth2b3253e2021-03-09 14:51:16 -0600155 }
Matthew Barth12cb1252021-03-08 16:47:30 -0600156}
157
Matthew Barth007de092021-03-25 13:56:04 -0500158void Zone::incTimerExpired()
159{
160 // Clear increase delta when timer expires allowing additional target
161 // increase requests or target decreases to occur
162 _incDelta = 0;
163}
164
Matthew Barth45c44ea2021-03-03 13:16:14 -0600165void Zone::requestDecrease(uint64_t targetDelta)
166{
167 // Only decrease the lowest target delta requested
168 if (_decDelta == 0 || targetDelta < _decDelta)
169 {
170 _decDelta = targetDelta;
171 }
172}
173
Matthew Barth007de092021-03-25 13:56:04 -0500174void Zone::decTimerExpired()
175{
176 // Check all entries are set to allow a decrease
177 auto pred = [](auto const& entry) { return entry.second; };
178 auto decAllowed = std::all_of(_decAllowed.begin(), _decAllowed.end(), pred);
179
180 // Only decrease targets when allowed, a requested decrease target delta
181 // exists, where no requested increases exist and the increase timer is not
182 // running (i.e. not in the middle of increasing)
183 if (decAllowed && _decDelta != 0 && _incDelta == 0 &&
184 !_incTimer.isEnabled())
185 {
186 auto requestTarget = getRequestTargetBase();
187 // Request target should not start above ceiling
188 if (requestTarget > _ceiling)
189 {
190 requestTarget = _ceiling;
191 }
192 // Target can not go below the defined floor
193 if ((requestTarget < _decDelta) || (requestTarget - _decDelta < _floor))
194 {
195 requestTarget = _floor;
196 }
197 else
198 {
199 requestTarget = requestTarget - _decDelta;
200 }
201 setTarget(requestTarget);
202 }
203 // Clear decrease delta when timer expires
204 _decDelta = 0;
205 // Decrease timer is restarted since its repeating
206}
207
Matthew Bartha0dd1352021-03-09 11:10:49 -0600208void Zone::setPersisted(const std::string& intf, const std::string& prop)
209{
210 if (std::find_if(_propsPersisted[intf].begin(), _propsPersisted[intf].end(),
211 [&prop](const auto& p) { return prop == p; }) !=
212 _propsPersisted[intf].end())
213 {
214 _propsPersisted[intf].emplace_back(prop);
215 }
216}
217
218std::string Zone::current(std::string value)
219{
220 auto current = ThermalObject::current();
221 std::transform(value.begin(), value.end(), value.begin(), toupper);
222
223 auto supported = ThermalObject::supported();
224 auto isSupported =
225 std::any_of(supported.begin(), supported.end(), [&value](auto& s) {
226 std::transform(s.begin(), s.end(), s.begin(), toupper);
227 return value == s;
228 });
229
230 if (value != current && isSupported)
231 {
232 current = ThermalObject::current(value);
Matthew Barth50219f52021-05-18 11:20:36 -0500233 if (isPersisted(thermModeIntf, currentProp))
Matthew Bartha0dd1352021-03-09 11:10:49 -0600234 {
235 saveCurrentMode();
236 }
Matthew Bartha0dd1352021-03-09 11:10:49 -0600237 }
238
239 return current;
240}
241
Matthew Barthe47c9582021-03-09 14:24:02 -0600242void Zone::setDefaultCeiling(const json& jsonObj)
Matthew Barth4f0d3b72020-08-27 14:32:15 -0500243{
Matthew Barth5606ec82021-05-17 12:08:52 -0500244 // TODO Remove "full_speed" after configs replaced with "default_ceiling"
245 if (!jsonObj.contains("full_speed") && !jsonObj.contains("default_ceiling"))
Matthew Barth4f0d3b72020-08-27 14:32:15 -0500246 {
Matthew Barth5606ec82021-05-17 12:08:52 -0500247 log<level::ERR>("Missing required zone's default ceiling",
Matthew Barth4f0d3b72020-08-27 14:32:15 -0500248 entry("JSON=%s", jsonObj.dump().c_str()));
Matthew Barth5606ec82021-05-17 12:08:52 -0500249 throw std::runtime_error("Missing required zone's default ceiling");
Matthew Barth4f0d3b72020-08-27 14:32:15 -0500250 }
Matthew Barth5606ec82021-05-17 12:08:52 -0500251 if (jsonObj.contains("full_speed"))
252 {
253 _defaultCeiling = jsonObj["full_speed"].get<uint64_t>();
254 }
255 else
256 {
257 _defaultCeiling = jsonObj["default_ceiling"].get<uint64_t>();
258 }
Matthew Barth12cb1252021-03-08 16:47:30 -0600259 // Start with the current target set as the default
Matthew Barthe47c9582021-03-09 14:24:02 -0600260 _target = _defaultCeiling;
Matthew Barth2b3253e2021-03-09 14:51:16 -0600261 // Start with the current ceiling set as the default
262 _ceiling = _defaultCeiling;
Matthew Barth4f0d3b72020-08-27 14:32:15 -0500263}
264
265void Zone::setDefaultFloor(const json& jsonObj)
266{
267 if (!jsonObj.contains("default_floor"))
268 {
Matthew Barthe47c9582021-03-09 14:24:02 -0600269 log<level::ERR>("Missing required zone's default floor",
Matthew Barth4f0d3b72020-08-27 14:32:15 -0500270 entry("JSON=%s", jsonObj.dump().c_str()));
Matthew Barthe47c9582021-03-09 14:24:02 -0600271 throw std::runtime_error("Missing required zone's default floor");
Matthew Barth4f0d3b72020-08-27 14:32:15 -0500272 }
273 _defaultFloor = jsonObj["default_floor"].get<uint64_t>();
Matthew Barth12cb1252021-03-08 16:47:30 -0600274 // Start with the current floor set as the default
275 _floor = _defaultFloor;
Matthew Barth4f0d3b72020-08-27 14:32:15 -0500276}
277
278void Zone::setDecInterval(const json& jsonObj)
279{
280 if (!jsonObj.contains("decrease_interval"))
281 {
282 log<level::ERR>("Missing required zone's decrease interval",
283 entry("JSON=%s", jsonObj.dump().c_str()));
284 throw std::runtime_error("Missing required zone's decrease interval");
285 }
Matthew Barth007de092021-03-25 13:56:04 -0500286 _decInterval =
287 std::chrono::seconds(jsonObj["decrease_interval"].get<uint64_t>());
Matthew Barth4f0d3b72020-08-27 14:32:15 -0500288}
289
Matthew Barth651f03a2020-08-27 16:15:11 -0500290void Zone::setInterfaces(const json& jsonObj)
291{
292 for (const auto& interface : jsonObj["interfaces"])
293 {
294 if (!interface.contains("name") || !interface.contains("properties"))
295 {
296 log<level::ERR>("Missing required zone interface attributes",
297 entry("JSON=%s", interface.dump().c_str()));
298 throw std::runtime_error(
299 "Missing required zone interface attributes");
300 }
Matthew Barth216229c2020-09-24 13:47:33 -0500301 auto propFuncs =
302 _intfPropHandlers.find(interface["name"].get<std::string>());
303 if (propFuncs == _intfPropHandlers.end())
304 {
305 // Construct list of available configurable interfaces
306 auto intfs = std::accumulate(
307 std::next(_intfPropHandlers.begin()), _intfPropHandlers.end(),
308 _intfPropHandlers.begin()->first, [](auto list, auto intf) {
309 return std::move(list) + ", " + intf.first;
310 });
311 log<level::ERR>("Configured interface not available",
312 entry("JSON=%s", interface.dump().c_str()),
313 entry("AVAILABLE_INTFS=%s", intfs.c_str()));
314 throw std::runtime_error("Configured interface not available");
315 }
316
Matthew Barth651f03a2020-08-27 16:15:11 -0500317 for (const auto& property : interface["properties"])
318 {
319 if (!property.contains("name"))
320 {
321 log<level::ERR>(
322 "Missing required interface property attributes",
323 entry("JSON=%s", property.dump().c_str()));
324 throw std::runtime_error(
325 "Missing required interface property attributes");
326 }
327 // Attribute "persist" is optional, defaults to `false`
328 auto persist = false;
329 if (property.contains("persist"))
330 {
331 persist = property["persist"].get<bool>();
332 }
333 // Property name from JSON must exactly match supported
334 // index names to functions in property namespace
Matthew Barth216229c2020-09-24 13:47:33 -0500335 auto propFunc =
336 propFuncs->second.find(property["name"].get<std::string>());
337 if (propFunc == propFuncs->second.end())
Matthew Barth651f03a2020-08-27 16:15:11 -0500338 {
Matthew Barth216229c2020-09-24 13:47:33 -0500339 // Construct list of available configurable properties
Matthew Barth651f03a2020-08-27 16:15:11 -0500340 auto props = std::accumulate(
Matthew Barth216229c2020-09-24 13:47:33 -0500341 std::next(propFuncs->second.begin()),
342 propFuncs->second.end(), propFuncs->second.begin()->first,
343 [](auto list, auto prop) {
Matthew Barth651f03a2020-08-27 16:15:11 -0500344 return std::move(list) + ", " + prop.first;
345 });
Matthew Barth216229c2020-09-24 13:47:33 -0500346 log<level::ERR>("Configured property not available",
Matthew Barth651f03a2020-08-27 16:15:11 -0500347 entry("JSON=%s", property.dump().c_str()),
348 entry("AVAILABLE_PROPS=%s", props.c_str()));
349 throw std::runtime_error(
350 "Configured property function not available");
351 }
Matthew Barthb584d812021-03-11 15:55:04 -0600352 auto propHandler = propFunc->second(property, persist);
353 // Only call non-null set property handler functions
354 if (propHandler)
355 {
356 propHandler(this);
357 }
Matthew Barth651f03a2020-08-27 16:15:11 -0500358 }
Matthew Barth651f03a2020-08-27 16:15:11 -0500359 }
360}
361
Matthew Bartha0dd1352021-03-09 11:10:49 -0600362bool Zone::isPersisted(const std::string& intf, const std::string& prop)
363{
364 auto it = _propsPersisted.find(intf);
365 if (it == _propsPersisted.end())
366 {
367 return false;
368 }
369
370 return std::any_of(it->second.begin(), it->second.end(),
371 [&prop](const auto& p) { return prop == p; });
372}
373
374void Zone::saveCurrentMode()
375{
376 fs::path path{CONTROL_PERSIST_ROOT_PATH};
377 // Append zone name and property description
378 path /= getName();
379 path /= "CurrentMode";
380 std::ofstream ofs(path.c_str(), std::ios::binary);
381 cereal::JSONOutputArchive oArch(ofs);
382 oArch(ThermalObject::current());
383}
384
Matthew Bartha4483742021-03-25 14:09:01 -0500385void Zone::restoreCurrentMode()
386{
387 auto current = ThermalObject::current();
388 fs::path path{CONTROL_PERSIST_ROOT_PATH};
389 path /= getName();
390 path /= "CurrentMode";
391 fs::create_directories(path.parent_path());
392
393 try
394 {
395 if (fs::exists(path))
396 {
397 std::ifstream ifs(path.c_str(), std::ios::in | std::ios::binary);
398 cereal::JSONInputArchive iArch(ifs);
399 iArch(current);
400 }
401 }
402 catch (std::exception& e)
403 {
404 log<level::ERR>(e.what());
405 fs::remove(path);
406 current = ThermalObject::current();
407 }
408
409 this->current(current);
410}
411
Matthew Barth651f03a2020-08-27 16:15:11 -0500412/**
Matthew Barth216229c2020-09-24 13:47:33 -0500413 * Properties of interfaces supported by the zone configuration that return
Matthew Barthb584d812021-03-11 15:55:04 -0600414 * a handler function that sets the zone's property value(s) and persist state.
Matthew Barth651f03a2020-08-27 16:15:11 -0500415 */
416namespace zone::property
417{
Matthew Barthb584d812021-03-11 15:55:04 -0600418// Get a set property handler function for the configured values of the
419// "Supported" property
420std::function<void(Zone*)> supported(const json& jsonObj, bool persist)
Matthew Barth651f03a2020-08-27 16:15:11 -0500421{
422 std::vector<std::string> values;
Matthew Barth216229c2020-09-24 13:47:33 -0500423 if (!jsonObj.contains("values"))
Matthew Barth651f03a2020-08-27 16:15:11 -0500424 {
Matthew Barth216229c2020-09-24 13:47:33 -0500425 log<level::ERR>(
426 "No 'values' found for \"Supported\" property, using an empty list",
427 entry("JSON=%s", jsonObj.dump().c_str()));
428 }
429 else
430 {
431 for (const auto& value : jsonObj["values"])
432 {
433 if (!value.contains("value"))
434 {
435 log<level::ERR>("No 'value' found for \"Supported\" property "
436 "entry, skipping",
437 entry("JSON=%s", value.dump().c_str()));
438 }
439 else
440 {
441 values.emplace_back(value["value"].get<std::string>());
442 }
443 }
Matthew Barth651f03a2020-08-27 16:15:11 -0500444 }
445
Matthew Barthb584d812021-03-11 15:55:04 -0600446 return Zone::setProperty<std::vector<std::string>>(
447 Zone::thermModeIntf, Zone::supportedProp, &Zone::supported,
448 std::move(values), persist);
Matthew Barth651f03a2020-08-27 16:15:11 -0500449}
450
Matthew Barthb584d812021-03-11 15:55:04 -0600451// Get a set property handler function for a configured value of the "Current"
Matthew Barth216229c2020-09-24 13:47:33 -0500452// property
Matthew Barthb584d812021-03-11 15:55:04 -0600453std::function<void(Zone*)> current(const json& jsonObj, bool persist)
Matthew Barth651f03a2020-08-27 16:15:11 -0500454{
Matthew Barth216229c2020-09-24 13:47:33 -0500455 // Use default value for "Current" property if no "value" entry given
456 if (!jsonObj.contains("value"))
457 {
Matthew Barthb584d812021-03-11 15:55:04 -0600458 log<level::INFO>("No 'value' found for \"Current\" property, "
459 "using default",
460 entry("JSON=%s", jsonObj.dump().c_str()));
461 // Set persist state of property
462 return Zone::setPropertyPersist(Zone::thermModeIntf, Zone::currentProp,
463 persist);
Matthew Barth216229c2020-09-24 13:47:33 -0500464 }
465
Matthew Barthb584d812021-03-11 15:55:04 -0600466 return Zone::setProperty<std::string>(
467 Zone::thermModeIntf, Zone::currentProp, &Zone::current,
468 jsonObj["value"].get<std::string>(), persist);
Matthew Barth651f03a2020-08-27 16:15:11 -0500469}
Matthew Barthb584d812021-03-11 15:55:04 -0600470
Matthew Barth651f03a2020-08-27 16:15:11 -0500471} // namespace zone::property
472
Matthew Barth4f0d3b72020-08-27 14:32:15 -0500473} // namespace phosphor::fan::control::json