blob: e11e6fc3911a12fc23e6b318d728abc3e4f75976 [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 Barth4f0d3b72020-08-27 14:32:15 -050027
Matthew Bartha0dd1352021-03-09 11:10:49 -060028#include <algorithm>
29#include <filesystem>
30#include <fstream>
Matthew Barth651f03a2020-08-27 16:15:11 -050031#include <iterator>
32#include <map>
33#include <numeric>
Matthew Barth651f03a2020-08-27 16:15:11 -050034#include <utility>
Matthew Barth216229c2020-09-24 13:47:33 -050035#include <vector>
Matthew Barth651f03a2020-08-27 16:15:11 -050036
Matthew Barth4f0d3b72020-08-27 14:32:15 -050037namespace phosphor::fan::control::json
38{
39
40using json = nlohmann::json;
41using namespace phosphor::logging;
Matthew Bartha0dd1352021-03-09 11:10:49 -060042namespace fs = std::filesystem;
Matthew Barth4f0d3b72020-08-27 14:32:15 -050043
Matthew Barthb584d812021-03-11 15:55:04 -060044const std::map<std::string,
45 std::map<std::string, std::function<std::function<void(Zone*)>(
46 const json&, bool)>>>
Matthew Barth216229c2020-09-24 13:47:33 -050047 Zone::_intfPropHandlers = {{thermModeIntf,
48 {{supportedProp, zone::property::supported},
49 {currentProp, zone::property::current}}}};
Matthew Barth651f03a2020-08-27 16:15:11 -050050
Matthew Barthacd737c2021-03-04 11:04:01 -060051Zone::Zone(sdbusplus::bus::bus& bus, const json& jsonObj) :
Matthew Bartha0dd1352021-03-09 11:10:49 -060052 ConfigBase(jsonObj),
53 ThermalObject(bus, (fs::path{CONTROL_OBJPATH} /= getName()).c_str(), true),
Matthew Barth45c44ea2021-03-03 13:16:14 -060054 _incDelay(0), _floor(0), _target(0), _incDelta(0), _decDelta(0),
55 _requestTargetBase(0), _isActive(true)
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 {
60 _incDelay = jsonObj["increase_delay"].get<uint64_t>();
61 }
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 Barth4f0d3b72020-08-27 14:32:15 -050070}
71
Matthew Barthde90fb42021-03-04 16:34:28 -060072void Zone::addFan(std::unique_ptr<Fan> fan)
73{
74 _fans.emplace_back(std::move(fan));
75}
76
Matthew Barth8ba715e2021-03-05 09:00:05 -060077void Zone::setTarget(uint64_t target)
78{
79 if (_isActive)
80 {
81 _target = target;
82 for (auto& fan : _fans)
83 {
84 fan->setTarget(_target);
85 }
86 }
87}
88
89void Zone::setActiveAllow(const std::string& ident, bool isActiveAllow)
90{
91 _active[ident] = isActiveAllow;
92 if (!isActiveAllow)
93 {
94 _isActive = false;
95 }
96 else
97 {
98 // Check all entries are set to allow active fan control
99 auto actPred = [](const auto& entry) { return entry.second; };
100 _isActive = std::all_of(_active.begin(), _active.end(), actPred);
101 }
102}
103
Matthew Barth12cb1252021-03-08 16:47:30 -0600104void Zone::setFloor(uint64_t target)
105{
106 // Check all entries are set to allow floor to be set
Matthew Barth8ba715e2021-03-05 09:00:05 -0600107 auto pred = [](const auto& entry) { return entry.second; };
Matthew Barth12cb1252021-03-08 16:47:30 -0600108 if (std::all_of(_floorChange.begin(), _floorChange.end(), pred))
109 {
110 _floor = target;
111 // Floor above target, update target to floor
112 if (_target < _floor)
113 {
114 requestIncrease(_floor - _target);
115 }
116 }
117}
118
119void Zone::requestIncrease(uint64_t targetDelta)
120{
Matthew Barth2b3253e2021-03-09 14:51:16 -0600121 // Only increase when delta is higher than the current increase delta for
122 // the zone and currently under ceiling
123 if (targetDelta > _incDelta && _target < _ceiling)
124 {
125 auto requestTarget = getRequestTargetBase();
126 requestTarget = (targetDelta - _incDelta) + requestTarget;
127 _incDelta = targetDelta;
128 // Target can not go above a current ceiling
129 if (requestTarget > _ceiling)
130 {
131 requestTarget = _ceiling;
132 }
Matthew Barth8ba715e2021-03-05 09:00:05 -0600133 setTarget(requestTarget);
134 // TODO // Restart timer countdown for fan speed increase
Matthew Barth2b3253e2021-03-09 14:51:16 -0600135 // _incTimer.restartOnce(_incDelay);
136 }
Matthew Barth12cb1252021-03-08 16:47:30 -0600137}
138
Matthew Barth45c44ea2021-03-03 13:16:14 -0600139void Zone::requestDecrease(uint64_t targetDelta)
140{
141 // Only decrease the lowest target delta requested
142 if (_decDelta == 0 || targetDelta < _decDelta)
143 {
144 _decDelta = targetDelta;
145 }
146}
147
Matthew Bartha0dd1352021-03-09 11:10:49 -0600148void Zone::setPersisted(const std::string& intf, const std::string& prop)
149{
150 if (std::find_if(_propsPersisted[intf].begin(), _propsPersisted[intf].end(),
151 [&prop](const auto& p) { return prop == p; }) !=
152 _propsPersisted[intf].end())
153 {
154 _propsPersisted[intf].emplace_back(prop);
155 }
156}
157
158std::string Zone::current(std::string value)
159{
160 auto current = ThermalObject::current();
161 std::transform(value.begin(), value.end(), value.begin(), toupper);
162
163 auto supported = ThermalObject::supported();
164 auto isSupported =
165 std::any_of(supported.begin(), supported.end(), [&value](auto& s) {
166 std::transform(s.begin(), s.end(), s.begin(), toupper);
167 return value == s;
168 });
169
170 if (value != current && isSupported)
171 {
172 current = ThermalObject::current(value);
173 if (isPersisted("xyz.openbmc_project.Control.ThermalMode", "Current"))
174 {
175 saveCurrentMode();
176 }
177 // TODO Trigger event(s) for current mode property change
178 // auto eData =
179 // _objects[_path]["xyz.openbmc_project.Control.ThermalMode"]
180 // ["Current"];
181 // if (eData != nullptr)
182 // {
183 // sdbusplus::message::message nullMsg{nullptr};
184 // handleEvent(nullMsg, eData);
185 // }
186 }
187
188 return current;
189}
190
Matthew Barthe47c9582021-03-09 14:24:02 -0600191void Zone::setDefaultCeiling(const json& jsonObj)
Matthew Barth4f0d3b72020-08-27 14:32:15 -0500192{
193 if (!jsonObj.contains("full_speed"))
194 {
195 log<level::ERR>("Missing required zone's full speed",
196 entry("JSON=%s", jsonObj.dump().c_str()));
197 throw std::runtime_error("Missing required zone's full speed");
198 }
Matthew Barthe47c9582021-03-09 14:24:02 -0600199 _defaultCeiling = jsonObj["full_speed"].get<uint64_t>();
Matthew Barth12cb1252021-03-08 16:47:30 -0600200 // Start with the current target set as the default
Matthew Barthe47c9582021-03-09 14:24:02 -0600201 _target = _defaultCeiling;
Matthew Barth2b3253e2021-03-09 14:51:16 -0600202 // Start with the current ceiling set as the default
203 _ceiling = _defaultCeiling;
Matthew Barth4f0d3b72020-08-27 14:32:15 -0500204}
205
206void Zone::setDefaultFloor(const json& jsonObj)
207{
208 if (!jsonObj.contains("default_floor"))
209 {
Matthew Barthe47c9582021-03-09 14:24:02 -0600210 log<level::ERR>("Missing required zone's default floor",
Matthew Barth4f0d3b72020-08-27 14:32:15 -0500211 entry("JSON=%s", jsonObj.dump().c_str()));
Matthew Barthe47c9582021-03-09 14:24:02 -0600212 throw std::runtime_error("Missing required zone's default floor");
Matthew Barth4f0d3b72020-08-27 14:32:15 -0500213 }
214 _defaultFloor = jsonObj["default_floor"].get<uint64_t>();
Matthew Barth12cb1252021-03-08 16:47:30 -0600215 // Start with the current floor set as the default
216 _floor = _defaultFloor;
Matthew Barth4f0d3b72020-08-27 14:32:15 -0500217}
218
219void Zone::setDecInterval(const json& jsonObj)
220{
221 if (!jsonObj.contains("decrease_interval"))
222 {
223 log<level::ERR>("Missing required zone's decrease interval",
224 entry("JSON=%s", jsonObj.dump().c_str()));
225 throw std::runtime_error("Missing required zone's decrease interval");
226 }
227 _decInterval = jsonObj["decrease_interval"].get<uint64_t>();
228}
229
Matthew Barth651f03a2020-08-27 16:15:11 -0500230void Zone::setInterfaces(const json& jsonObj)
231{
232 for (const auto& interface : jsonObj["interfaces"])
233 {
234 if (!interface.contains("name") || !interface.contains("properties"))
235 {
236 log<level::ERR>("Missing required zone interface attributes",
237 entry("JSON=%s", interface.dump().c_str()));
238 throw std::runtime_error(
239 "Missing required zone interface attributes");
240 }
Matthew Barth216229c2020-09-24 13:47:33 -0500241 auto propFuncs =
242 _intfPropHandlers.find(interface["name"].get<std::string>());
243 if (propFuncs == _intfPropHandlers.end())
244 {
245 // Construct list of available configurable interfaces
246 auto intfs = std::accumulate(
247 std::next(_intfPropHandlers.begin()), _intfPropHandlers.end(),
248 _intfPropHandlers.begin()->first, [](auto list, auto intf) {
249 return std::move(list) + ", " + intf.first;
250 });
251 log<level::ERR>("Configured interface not available",
252 entry("JSON=%s", interface.dump().c_str()),
253 entry("AVAILABLE_INTFS=%s", intfs.c_str()));
254 throw std::runtime_error("Configured interface not available");
255 }
256
Matthew Barth651f03a2020-08-27 16:15:11 -0500257 for (const auto& property : interface["properties"])
258 {
259 if (!property.contains("name"))
260 {
261 log<level::ERR>(
262 "Missing required interface property attributes",
263 entry("JSON=%s", property.dump().c_str()));
264 throw std::runtime_error(
265 "Missing required interface property attributes");
266 }
267 // Attribute "persist" is optional, defaults to `false`
268 auto persist = false;
269 if (property.contains("persist"))
270 {
271 persist = property["persist"].get<bool>();
272 }
273 // Property name from JSON must exactly match supported
274 // index names to functions in property namespace
Matthew Barth216229c2020-09-24 13:47:33 -0500275 auto propFunc =
276 propFuncs->second.find(property["name"].get<std::string>());
277 if (propFunc == propFuncs->second.end())
Matthew Barth651f03a2020-08-27 16:15:11 -0500278 {
Matthew Barth216229c2020-09-24 13:47:33 -0500279 // Construct list of available configurable properties
Matthew Barth651f03a2020-08-27 16:15:11 -0500280 auto props = std::accumulate(
Matthew Barth216229c2020-09-24 13:47:33 -0500281 std::next(propFuncs->second.begin()),
282 propFuncs->second.end(), propFuncs->second.begin()->first,
283 [](auto list, auto prop) {
Matthew Barth651f03a2020-08-27 16:15:11 -0500284 return std::move(list) + ", " + prop.first;
285 });
Matthew Barth216229c2020-09-24 13:47:33 -0500286 log<level::ERR>("Configured property not available",
Matthew Barth651f03a2020-08-27 16:15:11 -0500287 entry("JSON=%s", property.dump().c_str()),
288 entry("AVAILABLE_PROPS=%s", props.c_str()));
289 throw std::runtime_error(
290 "Configured property function not available");
291 }
Matthew Barthb584d812021-03-11 15:55:04 -0600292 auto propHandler = propFunc->second(property, persist);
293 // Only call non-null set property handler functions
294 if (propHandler)
295 {
296 propHandler(this);
297 }
Matthew Barth651f03a2020-08-27 16:15:11 -0500298 }
Matthew Barth651f03a2020-08-27 16:15:11 -0500299 }
300}
301
Matthew Bartha0dd1352021-03-09 11:10:49 -0600302bool Zone::isPersisted(const std::string& intf, const std::string& prop)
303{
304 auto it = _propsPersisted.find(intf);
305 if (it == _propsPersisted.end())
306 {
307 return false;
308 }
309
310 return std::any_of(it->second.begin(), it->second.end(),
311 [&prop](const auto& p) { return prop == p; });
312}
313
314void Zone::saveCurrentMode()
315{
316 fs::path path{CONTROL_PERSIST_ROOT_PATH};
317 // Append zone name and property description
318 path /= getName();
319 path /= "CurrentMode";
320 std::ofstream ofs(path.c_str(), std::ios::binary);
321 cereal::JSONOutputArchive oArch(ofs);
322 oArch(ThermalObject::current());
323}
324
Matthew Barth651f03a2020-08-27 16:15:11 -0500325/**
Matthew Barth216229c2020-09-24 13:47:33 -0500326 * Properties of interfaces supported by the zone configuration that return
Matthew Barthb584d812021-03-11 15:55:04 -0600327 * a handler function that sets the zone's property value(s) and persist state.
Matthew Barth651f03a2020-08-27 16:15:11 -0500328 */
329namespace zone::property
330{
Matthew Barthb584d812021-03-11 15:55:04 -0600331// Get a set property handler function for the configured values of the
332// "Supported" property
333std::function<void(Zone*)> supported(const json& jsonObj, bool persist)
Matthew Barth651f03a2020-08-27 16:15:11 -0500334{
335 std::vector<std::string> values;
Matthew Barth216229c2020-09-24 13:47:33 -0500336 if (!jsonObj.contains("values"))
Matthew Barth651f03a2020-08-27 16:15:11 -0500337 {
Matthew Barth216229c2020-09-24 13:47:33 -0500338 log<level::ERR>(
339 "No 'values' found for \"Supported\" property, using an empty list",
340 entry("JSON=%s", jsonObj.dump().c_str()));
341 }
342 else
343 {
344 for (const auto& value : jsonObj["values"])
345 {
346 if (!value.contains("value"))
347 {
348 log<level::ERR>("No 'value' found for \"Supported\" property "
349 "entry, skipping",
350 entry("JSON=%s", value.dump().c_str()));
351 }
352 else
353 {
354 values.emplace_back(value["value"].get<std::string>());
355 }
356 }
Matthew Barth651f03a2020-08-27 16:15:11 -0500357 }
358
Matthew Barthb584d812021-03-11 15:55:04 -0600359 return Zone::setProperty<std::vector<std::string>>(
360 Zone::thermModeIntf, Zone::supportedProp, &Zone::supported,
361 std::move(values), persist);
Matthew Barth651f03a2020-08-27 16:15:11 -0500362}
363
Matthew Barthb584d812021-03-11 15:55:04 -0600364// Get a set property handler function for a configured value of the "Current"
Matthew Barth216229c2020-09-24 13:47:33 -0500365// property
Matthew Barthb584d812021-03-11 15:55:04 -0600366std::function<void(Zone*)> current(const json& jsonObj, bool persist)
Matthew Barth651f03a2020-08-27 16:15:11 -0500367{
Matthew Barth216229c2020-09-24 13:47:33 -0500368 // Use default value for "Current" property if no "value" entry given
369 if (!jsonObj.contains("value"))
370 {
Matthew Barthb584d812021-03-11 15:55:04 -0600371 log<level::INFO>("No 'value' found for \"Current\" property, "
372 "using default",
373 entry("JSON=%s", jsonObj.dump().c_str()));
374 // Set persist state of property
375 return Zone::setPropertyPersist(Zone::thermModeIntf, Zone::currentProp,
376 persist);
Matthew Barth216229c2020-09-24 13:47:33 -0500377 }
378
Matthew Barthb584d812021-03-11 15:55:04 -0600379 return Zone::setProperty<std::string>(
380 Zone::thermModeIntf, Zone::currentProp, &Zone::current,
381 jsonObj["value"].get<std::string>(), persist);
Matthew Barth651f03a2020-08-27 16:15:11 -0500382}
Matthew Barthb584d812021-03-11 15:55:04 -0600383
Matthew Barth651f03a2020-08-27 16:15:11 -0500384} // namespace zone::property
385
Matthew Barth4f0d3b72020-08-27 14:32:15 -0500386} // namespace phosphor::fan::control::json