blob: be827cf98ffc7a3652fbf83c51c21d571e8ceb02 [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 Barth216229c2020-09-24 13:47:33 -050021
Matthew Bartha0dd1352021-03-09 11:10:49 -060022#include <cereal/archives/json.hpp>
23#include <cereal/cereal.hpp>
Matthew Barth4f0d3b72020-08-27 14:32:15 -050024#include <nlohmann/json.hpp>
25#include <phosphor-logging/log.hpp>
Matthew Barthacd737c2021-03-04 11:04:01 -060026#include <sdbusplus/bus.hpp>
Matthew Barth603ef162021-03-24 15:34:53 -050027#include <sdeventplus/event.hpp>
Matthew Barth4f0d3b72020-08-27 14:32:15 -050028
Matthew Bartha0dd1352021-03-09 11:10:49 -060029#include <algorithm>
Matthew Barth007de092021-03-25 13:56:04 -050030#include <chrono>
Matthew Bartha0dd1352021-03-09 11:10:49 -060031#include <filesystem>
32#include <fstream>
Matthew Barth651f03a2020-08-27 16:15:11 -050033#include <iterator>
34#include <map>
35#include <numeric>
Matthew Barth651f03a2020-08-27 16:15:11 -050036#include <utility>
Matthew Barth216229c2020-09-24 13:47:33 -050037#include <vector>
Matthew Barth651f03a2020-08-27 16:15:11 -050038
Matthew Barth4f0d3b72020-08-27 14:32:15 -050039namespace phosphor::fan::control::json
40{
41
42using json = nlohmann::json;
43using namespace phosphor::logging;
Matthew Bartha0dd1352021-03-09 11:10:49 -060044namespace fs = std::filesystem;
Matthew Barth4f0d3b72020-08-27 14:32:15 -050045
Matthew Barthb584d812021-03-11 15:55:04 -060046const std::map<std::string,
47 std::map<std::string, std::function<std::function<void(Zone*)>(
48 const json&, bool)>>>
Matthew Barth216229c2020-09-24 13:47:33 -050049 Zone::_intfPropHandlers = {{thermModeIntf,
50 {{supportedProp, zone::property::supported},
51 {currentProp, zone::property::current}}}};
Matthew Barth651f03a2020-08-27 16:15:11 -050052
Matthew Barth603ef162021-03-24 15:34:53 -050053Zone::Zone(const json& jsonObj, sdbusplus::bus::bus& bus,
54 const sdeventplus::Event& event, Manager* mgr) :
Matthew Bartha0dd1352021-03-09 11:10:49 -060055 ConfigBase(jsonObj),
56 ThermalObject(bus, (fs::path{CONTROL_OBJPATH} /= getName()).c_str(), true),
Matthew Barth603ef162021-03-24 15:34:53 -050057 _manager(mgr), _incDelay(0), _floor(0), _target(0), _incDelta(0),
Matthew Barth007de092021-03-25 13:56:04 -050058 _decDelta(0), _requestTargetBase(0), _isActive(true),
59 _incTimer(event, std::bind(&Zone::incTimerExpired, this)),
60 _decTimer(event, std::bind(&Zone::decTimerExpired, this))
Matthew Barth4f0d3b72020-08-27 14:32:15 -050061{
Matthew Barthe47c9582021-03-09 14:24:02 -060062 // Increase delay is optional, defaults to 0
Matthew Barth4f0d3b72020-08-27 14:32:15 -050063 if (jsonObj.contains("increase_delay"))
64 {
Matthew Barth007de092021-03-25 13:56:04 -050065 _incDelay =
66 std::chrono::seconds(jsonObj["increase_delay"].get<uint64_t>());
Matthew Barth4f0d3b72020-08-27 14:32:15 -050067 }
Matthew Barthe47c9582021-03-09 14:24:02 -060068 setDefaultCeiling(jsonObj);
Matthew Barth4f0d3b72020-08-27 14:32:15 -050069 setDefaultFloor(jsonObj);
70 setDecInterval(jsonObj);
Matthew Barth651f03a2020-08-27 16:15:11 -050071 // Setting properties on interfaces to be served are optional
72 if (jsonObj.contains("interfaces"))
73 {
74 setInterfaces(jsonObj);
75 }
Matthew Barth007de092021-03-25 13:56:04 -050076
77 // Start timer for fan target decreases
78 _decTimer.restart(_decInterval);
Matthew Barth4f0d3b72020-08-27 14:32:15 -050079}
80
Matthew Barthde90fb42021-03-04 16:34:28 -060081void Zone::addFan(std::unique_ptr<Fan> fan)
82{
83 _fans.emplace_back(std::move(fan));
84}
85
Matthew Barth8ba715e2021-03-05 09:00:05 -060086void Zone::setTarget(uint64_t target)
87{
88 if (_isActive)
89 {
90 _target = target;
91 for (auto& fan : _fans)
92 {
93 fan->setTarget(_target);
94 }
95 }
96}
97
98void Zone::setActiveAllow(const std::string& ident, bool isActiveAllow)
99{
100 _active[ident] = isActiveAllow;
101 if (!isActiveAllow)
102 {
103 _isActive = false;
104 }
105 else
106 {
107 // Check all entries are set to allow active fan control
108 auto actPred = [](const auto& entry) { return entry.second; };
109 _isActive = std::all_of(_active.begin(), _active.end(), actPred);
110 }
111}
112
Matthew Barth12cb1252021-03-08 16:47:30 -0600113void Zone::setFloor(uint64_t target)
114{
115 // Check all entries are set to allow floor to be set
Matthew Barth8ba715e2021-03-05 09:00:05 -0600116 auto pred = [](const auto& entry) { return entry.second; };
Matthew Barth12cb1252021-03-08 16:47:30 -0600117 if (std::all_of(_floorChange.begin(), _floorChange.end(), pred))
118 {
119 _floor = target;
120 // Floor above target, update target to floor
121 if (_target < _floor)
122 {
123 requestIncrease(_floor - _target);
124 }
125 }
126}
127
128void Zone::requestIncrease(uint64_t targetDelta)
129{
Matthew Barth2b3253e2021-03-09 14:51:16 -0600130 // Only increase when delta is higher than the current increase delta for
131 // the zone and currently under ceiling
132 if (targetDelta > _incDelta && _target < _ceiling)
133 {
134 auto requestTarget = getRequestTargetBase();
135 requestTarget = (targetDelta - _incDelta) + requestTarget;
136 _incDelta = targetDelta;
137 // Target can not go above a current ceiling
138 if (requestTarget > _ceiling)
139 {
140 requestTarget = _ceiling;
141 }
Matthew Barth8ba715e2021-03-05 09:00:05 -0600142 setTarget(requestTarget);
Matthew Barth007de092021-03-25 13:56:04 -0500143 // Restart timer countdown for fan target increase
144 _incTimer.restartOnce(_incDelay);
Matthew Barth2b3253e2021-03-09 14:51:16 -0600145 }
Matthew Barth12cb1252021-03-08 16:47:30 -0600146}
147
Matthew Barth007de092021-03-25 13:56:04 -0500148void Zone::incTimerExpired()
149{
150 // Clear increase delta when timer expires allowing additional target
151 // increase requests or target decreases to occur
152 _incDelta = 0;
153}
154
Matthew Barth45c44ea2021-03-03 13:16:14 -0600155void Zone::requestDecrease(uint64_t targetDelta)
156{
157 // Only decrease the lowest target delta requested
158 if (_decDelta == 0 || targetDelta < _decDelta)
159 {
160 _decDelta = targetDelta;
161 }
162}
163
Matthew Barth007de092021-03-25 13:56:04 -0500164void Zone::decTimerExpired()
165{
166 // Check all entries are set to allow a decrease
167 auto pred = [](auto const& entry) { return entry.second; };
168 auto decAllowed = std::all_of(_decAllowed.begin(), _decAllowed.end(), pred);
169
170 // Only decrease targets when allowed, a requested decrease target delta
171 // exists, where no requested increases exist and the increase timer is not
172 // running (i.e. not in the middle of increasing)
173 if (decAllowed && _decDelta != 0 && _incDelta == 0 &&
174 !_incTimer.isEnabled())
175 {
176 auto requestTarget = getRequestTargetBase();
177 // Request target should not start above ceiling
178 if (requestTarget > _ceiling)
179 {
180 requestTarget = _ceiling;
181 }
182 // Target can not go below the defined floor
183 if ((requestTarget < _decDelta) || (requestTarget - _decDelta < _floor))
184 {
185 requestTarget = _floor;
186 }
187 else
188 {
189 requestTarget = requestTarget - _decDelta;
190 }
191 setTarget(requestTarget);
192 }
193 // Clear decrease delta when timer expires
194 _decDelta = 0;
195 // Decrease timer is restarted since its repeating
196}
197
Matthew Bartha0dd1352021-03-09 11:10:49 -0600198void Zone::setPersisted(const std::string& intf, const std::string& prop)
199{
200 if (std::find_if(_propsPersisted[intf].begin(), _propsPersisted[intf].end(),
201 [&prop](const auto& p) { return prop == p; }) !=
202 _propsPersisted[intf].end())
203 {
204 _propsPersisted[intf].emplace_back(prop);
205 }
206}
207
208std::string Zone::current(std::string value)
209{
210 auto current = ThermalObject::current();
211 std::transform(value.begin(), value.end(), value.begin(), toupper);
212
213 auto supported = ThermalObject::supported();
214 auto isSupported =
215 std::any_of(supported.begin(), supported.end(), [&value](auto& s) {
216 std::transform(s.begin(), s.end(), s.begin(), toupper);
217 return value == s;
218 });
219
220 if (value != current && isSupported)
221 {
222 current = ThermalObject::current(value);
223 if (isPersisted("xyz.openbmc_project.Control.ThermalMode", "Current"))
224 {
225 saveCurrentMode();
226 }
227 // TODO Trigger event(s) for current mode property change
228 // auto eData =
229 // _objects[_path]["xyz.openbmc_project.Control.ThermalMode"]
230 // ["Current"];
231 // if (eData != nullptr)
232 // {
233 // sdbusplus::message::message nullMsg{nullptr};
234 // handleEvent(nullMsg, eData);
235 // }
236 }
237
238 return current;
239}
240
Matthew Barthe47c9582021-03-09 14:24:02 -0600241void Zone::setDefaultCeiling(const json& jsonObj)
Matthew Barth4f0d3b72020-08-27 14:32:15 -0500242{
243 if (!jsonObj.contains("full_speed"))
244 {
245 log<level::ERR>("Missing required zone's full speed",
246 entry("JSON=%s", jsonObj.dump().c_str()));
247 throw std::runtime_error("Missing required zone's full speed");
248 }
Matthew Barthe47c9582021-03-09 14:24:02 -0600249 _defaultCeiling = jsonObj["full_speed"].get<uint64_t>();
Matthew Barth12cb1252021-03-08 16:47:30 -0600250 // Start with the current target set as the default
Matthew Barthe47c9582021-03-09 14:24:02 -0600251 _target = _defaultCeiling;
Matthew Barth2b3253e2021-03-09 14:51:16 -0600252 // Start with the current ceiling set as the default
253 _ceiling = _defaultCeiling;
Matthew Barth4f0d3b72020-08-27 14:32:15 -0500254}
255
256void Zone::setDefaultFloor(const json& jsonObj)
257{
258 if (!jsonObj.contains("default_floor"))
259 {
Matthew Barthe47c9582021-03-09 14:24:02 -0600260 log<level::ERR>("Missing required zone's default floor",
Matthew Barth4f0d3b72020-08-27 14:32:15 -0500261 entry("JSON=%s", jsonObj.dump().c_str()));
Matthew Barthe47c9582021-03-09 14:24:02 -0600262 throw std::runtime_error("Missing required zone's default floor");
Matthew Barth4f0d3b72020-08-27 14:32:15 -0500263 }
264 _defaultFloor = jsonObj["default_floor"].get<uint64_t>();
Matthew Barth12cb1252021-03-08 16:47:30 -0600265 // Start with the current floor set as the default
266 _floor = _defaultFloor;
Matthew Barth4f0d3b72020-08-27 14:32:15 -0500267}
268
269void Zone::setDecInterval(const json& jsonObj)
270{
271 if (!jsonObj.contains("decrease_interval"))
272 {
273 log<level::ERR>("Missing required zone's decrease interval",
274 entry("JSON=%s", jsonObj.dump().c_str()));
275 throw std::runtime_error("Missing required zone's decrease interval");
276 }
Matthew Barth007de092021-03-25 13:56:04 -0500277 _decInterval =
278 std::chrono::seconds(jsonObj["decrease_interval"].get<uint64_t>());
Matthew Barth4f0d3b72020-08-27 14:32:15 -0500279}
280
Matthew Barth651f03a2020-08-27 16:15:11 -0500281void Zone::setInterfaces(const json& jsonObj)
282{
283 for (const auto& interface : jsonObj["interfaces"])
284 {
285 if (!interface.contains("name") || !interface.contains("properties"))
286 {
287 log<level::ERR>("Missing required zone interface attributes",
288 entry("JSON=%s", interface.dump().c_str()));
289 throw std::runtime_error(
290 "Missing required zone interface attributes");
291 }
Matthew Barth216229c2020-09-24 13:47:33 -0500292 auto propFuncs =
293 _intfPropHandlers.find(interface["name"].get<std::string>());
294 if (propFuncs == _intfPropHandlers.end())
295 {
296 // Construct list of available configurable interfaces
297 auto intfs = std::accumulate(
298 std::next(_intfPropHandlers.begin()), _intfPropHandlers.end(),
299 _intfPropHandlers.begin()->first, [](auto list, auto intf) {
300 return std::move(list) + ", " + intf.first;
301 });
302 log<level::ERR>("Configured interface not available",
303 entry("JSON=%s", interface.dump().c_str()),
304 entry("AVAILABLE_INTFS=%s", intfs.c_str()));
305 throw std::runtime_error("Configured interface not available");
306 }
307
Matthew Barth651f03a2020-08-27 16:15:11 -0500308 for (const auto& property : interface["properties"])
309 {
310 if (!property.contains("name"))
311 {
312 log<level::ERR>(
313 "Missing required interface property attributes",
314 entry("JSON=%s", property.dump().c_str()));
315 throw std::runtime_error(
316 "Missing required interface property attributes");
317 }
318 // Attribute "persist" is optional, defaults to `false`
319 auto persist = false;
320 if (property.contains("persist"))
321 {
322 persist = property["persist"].get<bool>();
323 }
324 // Property name from JSON must exactly match supported
325 // index names to functions in property namespace
Matthew Barth216229c2020-09-24 13:47:33 -0500326 auto propFunc =
327 propFuncs->second.find(property["name"].get<std::string>());
328 if (propFunc == propFuncs->second.end())
Matthew Barth651f03a2020-08-27 16:15:11 -0500329 {
Matthew Barth216229c2020-09-24 13:47:33 -0500330 // Construct list of available configurable properties
Matthew Barth651f03a2020-08-27 16:15:11 -0500331 auto props = std::accumulate(
Matthew Barth216229c2020-09-24 13:47:33 -0500332 std::next(propFuncs->second.begin()),
333 propFuncs->second.end(), propFuncs->second.begin()->first,
334 [](auto list, auto prop) {
Matthew Barth651f03a2020-08-27 16:15:11 -0500335 return std::move(list) + ", " + prop.first;
336 });
Matthew Barth216229c2020-09-24 13:47:33 -0500337 log<level::ERR>("Configured property not available",
Matthew Barth651f03a2020-08-27 16:15:11 -0500338 entry("JSON=%s", property.dump().c_str()),
339 entry("AVAILABLE_PROPS=%s", props.c_str()));
340 throw std::runtime_error(
341 "Configured property function not available");
342 }
Matthew Barthb584d812021-03-11 15:55:04 -0600343 auto propHandler = propFunc->second(property, persist);
344 // Only call non-null set property handler functions
345 if (propHandler)
346 {
347 propHandler(this);
348 }
Matthew Barth651f03a2020-08-27 16:15:11 -0500349 }
Matthew Barth651f03a2020-08-27 16:15:11 -0500350 }
351}
352
Matthew Bartha0dd1352021-03-09 11:10:49 -0600353bool Zone::isPersisted(const std::string& intf, const std::string& prop)
354{
355 auto it = _propsPersisted.find(intf);
356 if (it == _propsPersisted.end())
357 {
358 return false;
359 }
360
361 return std::any_of(it->second.begin(), it->second.end(),
362 [&prop](const auto& p) { return prop == p; });
363}
364
365void Zone::saveCurrentMode()
366{
367 fs::path path{CONTROL_PERSIST_ROOT_PATH};
368 // Append zone name and property description
369 path /= getName();
370 path /= "CurrentMode";
371 std::ofstream ofs(path.c_str(), std::ios::binary);
372 cereal::JSONOutputArchive oArch(ofs);
373 oArch(ThermalObject::current());
374}
375
Matthew Barth651f03a2020-08-27 16:15:11 -0500376/**
Matthew Barth216229c2020-09-24 13:47:33 -0500377 * Properties of interfaces supported by the zone configuration that return
Matthew Barthb584d812021-03-11 15:55:04 -0600378 * a handler function that sets the zone's property value(s) and persist state.
Matthew Barth651f03a2020-08-27 16:15:11 -0500379 */
380namespace zone::property
381{
Matthew Barthb584d812021-03-11 15:55:04 -0600382// Get a set property handler function for the configured values of the
383// "Supported" property
384std::function<void(Zone*)> supported(const json& jsonObj, bool persist)
Matthew Barth651f03a2020-08-27 16:15:11 -0500385{
386 std::vector<std::string> values;
Matthew Barth216229c2020-09-24 13:47:33 -0500387 if (!jsonObj.contains("values"))
Matthew Barth651f03a2020-08-27 16:15:11 -0500388 {
Matthew Barth216229c2020-09-24 13:47:33 -0500389 log<level::ERR>(
390 "No 'values' found for \"Supported\" property, using an empty list",
391 entry("JSON=%s", jsonObj.dump().c_str()));
392 }
393 else
394 {
395 for (const auto& value : jsonObj["values"])
396 {
397 if (!value.contains("value"))
398 {
399 log<level::ERR>("No 'value' found for \"Supported\" property "
400 "entry, skipping",
401 entry("JSON=%s", value.dump().c_str()));
402 }
403 else
404 {
405 values.emplace_back(value["value"].get<std::string>());
406 }
407 }
Matthew Barth651f03a2020-08-27 16:15:11 -0500408 }
409
Matthew Barthb584d812021-03-11 15:55:04 -0600410 return Zone::setProperty<std::vector<std::string>>(
411 Zone::thermModeIntf, Zone::supportedProp, &Zone::supported,
412 std::move(values), persist);
Matthew Barth651f03a2020-08-27 16:15:11 -0500413}
414
Matthew Barthb584d812021-03-11 15:55:04 -0600415// Get a set property handler function for a configured value of the "Current"
Matthew Barth216229c2020-09-24 13:47:33 -0500416// property
Matthew Barthb584d812021-03-11 15:55:04 -0600417std::function<void(Zone*)> current(const json& jsonObj, bool persist)
Matthew Barth651f03a2020-08-27 16:15:11 -0500418{
Matthew Barth216229c2020-09-24 13:47:33 -0500419 // Use default value for "Current" property if no "value" entry given
420 if (!jsonObj.contains("value"))
421 {
Matthew Barthb584d812021-03-11 15:55:04 -0600422 log<level::INFO>("No 'value' found for \"Current\" property, "
423 "using default",
424 entry("JSON=%s", jsonObj.dump().c_str()));
425 // Set persist state of property
426 return Zone::setPropertyPersist(Zone::thermModeIntf, Zone::currentProp,
427 persist);
Matthew Barth216229c2020-09-24 13:47:33 -0500428 }
429
Matthew Barthb584d812021-03-11 15:55:04 -0600430 return Zone::setProperty<std::string>(
431 Zone::thermModeIntf, Zone::currentProp, &Zone::current,
432 jsonObj["value"].get<std::string>(), persist);
Matthew Barth651f03a2020-08-27 16:15:11 -0500433}
Matthew Barthb584d812021-03-11 15:55:04 -0600434
Matthew Barth651f03a2020-08-27 16:15:11 -0500435} // namespace zone::property
436
Matthew Barth4f0d3b72020-08-27 14:32:15 -0500437} // namespace phosphor::fan::control::json