blob: 6dd4618431180dd5ba617c54c7740e25916b23a1 [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 Barth8ba715e2021-03-05 09:00:05 -060054 _incDelay(0), _floor(0), _target(0), _incDelta(0), _requestTargetBase(0),
55 _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 Bartha0dd1352021-03-09 11:10:49 -0600139void Zone::setPersisted(const std::string& intf, const std::string& prop)
140{
141 if (std::find_if(_propsPersisted[intf].begin(), _propsPersisted[intf].end(),
142 [&prop](const auto& p) { return prop == p; }) !=
143 _propsPersisted[intf].end())
144 {
145 _propsPersisted[intf].emplace_back(prop);
146 }
147}
148
149std::string Zone::current(std::string value)
150{
151 auto current = ThermalObject::current();
152 std::transform(value.begin(), value.end(), value.begin(), toupper);
153
154 auto supported = ThermalObject::supported();
155 auto isSupported =
156 std::any_of(supported.begin(), supported.end(), [&value](auto& s) {
157 std::transform(s.begin(), s.end(), s.begin(), toupper);
158 return value == s;
159 });
160
161 if (value != current && isSupported)
162 {
163 current = ThermalObject::current(value);
164 if (isPersisted("xyz.openbmc_project.Control.ThermalMode", "Current"))
165 {
166 saveCurrentMode();
167 }
168 // TODO Trigger event(s) for current mode property change
169 // auto eData =
170 // _objects[_path]["xyz.openbmc_project.Control.ThermalMode"]
171 // ["Current"];
172 // if (eData != nullptr)
173 // {
174 // sdbusplus::message::message nullMsg{nullptr};
175 // handleEvent(nullMsg, eData);
176 // }
177 }
178
179 return current;
180}
181
Matthew Barthe47c9582021-03-09 14:24:02 -0600182void Zone::setDefaultCeiling(const json& jsonObj)
Matthew Barth4f0d3b72020-08-27 14:32:15 -0500183{
184 if (!jsonObj.contains("full_speed"))
185 {
186 log<level::ERR>("Missing required zone's full speed",
187 entry("JSON=%s", jsonObj.dump().c_str()));
188 throw std::runtime_error("Missing required zone's full speed");
189 }
Matthew Barthe47c9582021-03-09 14:24:02 -0600190 _defaultCeiling = jsonObj["full_speed"].get<uint64_t>();
Matthew Barth12cb1252021-03-08 16:47:30 -0600191 // Start with the current target set as the default
Matthew Barthe47c9582021-03-09 14:24:02 -0600192 _target = _defaultCeiling;
Matthew Barth2b3253e2021-03-09 14:51:16 -0600193 // Start with the current ceiling set as the default
194 _ceiling = _defaultCeiling;
Matthew Barth4f0d3b72020-08-27 14:32:15 -0500195}
196
197void Zone::setDefaultFloor(const json& jsonObj)
198{
199 if (!jsonObj.contains("default_floor"))
200 {
Matthew Barthe47c9582021-03-09 14:24:02 -0600201 log<level::ERR>("Missing required zone's default floor",
Matthew Barth4f0d3b72020-08-27 14:32:15 -0500202 entry("JSON=%s", jsonObj.dump().c_str()));
Matthew Barthe47c9582021-03-09 14:24:02 -0600203 throw std::runtime_error("Missing required zone's default floor");
Matthew Barth4f0d3b72020-08-27 14:32:15 -0500204 }
205 _defaultFloor = jsonObj["default_floor"].get<uint64_t>();
Matthew Barth12cb1252021-03-08 16:47:30 -0600206 // Start with the current floor set as the default
207 _floor = _defaultFloor;
Matthew Barth4f0d3b72020-08-27 14:32:15 -0500208}
209
210void Zone::setDecInterval(const json& jsonObj)
211{
212 if (!jsonObj.contains("decrease_interval"))
213 {
214 log<level::ERR>("Missing required zone's decrease interval",
215 entry("JSON=%s", jsonObj.dump().c_str()));
216 throw std::runtime_error("Missing required zone's decrease interval");
217 }
218 _decInterval = jsonObj["decrease_interval"].get<uint64_t>();
219}
220
Matthew Barth651f03a2020-08-27 16:15:11 -0500221void Zone::setInterfaces(const json& jsonObj)
222{
223 for (const auto& interface : jsonObj["interfaces"])
224 {
225 if (!interface.contains("name") || !interface.contains("properties"))
226 {
227 log<level::ERR>("Missing required zone interface attributes",
228 entry("JSON=%s", interface.dump().c_str()));
229 throw std::runtime_error(
230 "Missing required zone interface attributes");
231 }
Matthew Barth216229c2020-09-24 13:47:33 -0500232 auto propFuncs =
233 _intfPropHandlers.find(interface["name"].get<std::string>());
234 if (propFuncs == _intfPropHandlers.end())
235 {
236 // Construct list of available configurable interfaces
237 auto intfs = std::accumulate(
238 std::next(_intfPropHandlers.begin()), _intfPropHandlers.end(),
239 _intfPropHandlers.begin()->first, [](auto list, auto intf) {
240 return std::move(list) + ", " + intf.first;
241 });
242 log<level::ERR>("Configured interface not available",
243 entry("JSON=%s", interface.dump().c_str()),
244 entry("AVAILABLE_INTFS=%s", intfs.c_str()));
245 throw std::runtime_error("Configured interface not available");
246 }
247
Matthew Barth651f03a2020-08-27 16:15:11 -0500248 for (const auto& property : interface["properties"])
249 {
250 if (!property.contains("name"))
251 {
252 log<level::ERR>(
253 "Missing required interface property attributes",
254 entry("JSON=%s", property.dump().c_str()));
255 throw std::runtime_error(
256 "Missing required interface property attributes");
257 }
258 // Attribute "persist" is optional, defaults to `false`
259 auto persist = false;
260 if (property.contains("persist"))
261 {
262 persist = property["persist"].get<bool>();
263 }
264 // Property name from JSON must exactly match supported
265 // index names to functions in property namespace
Matthew Barth216229c2020-09-24 13:47:33 -0500266 auto propFunc =
267 propFuncs->second.find(property["name"].get<std::string>());
268 if (propFunc == propFuncs->second.end())
Matthew Barth651f03a2020-08-27 16:15:11 -0500269 {
Matthew Barth216229c2020-09-24 13:47:33 -0500270 // Construct list of available configurable properties
Matthew Barth651f03a2020-08-27 16:15:11 -0500271 auto props = std::accumulate(
Matthew Barth216229c2020-09-24 13:47:33 -0500272 std::next(propFuncs->second.begin()),
273 propFuncs->second.end(), propFuncs->second.begin()->first,
274 [](auto list, auto prop) {
Matthew Barth651f03a2020-08-27 16:15:11 -0500275 return std::move(list) + ", " + prop.first;
276 });
Matthew Barth216229c2020-09-24 13:47:33 -0500277 log<level::ERR>("Configured property not available",
Matthew Barth651f03a2020-08-27 16:15:11 -0500278 entry("JSON=%s", property.dump().c_str()),
279 entry("AVAILABLE_PROPS=%s", props.c_str()));
280 throw std::runtime_error(
281 "Configured property function not available");
282 }
Matthew Barthb584d812021-03-11 15:55:04 -0600283 auto propHandler = propFunc->second(property, persist);
284 // Only call non-null set property handler functions
285 if (propHandler)
286 {
287 propHandler(this);
288 }
Matthew Barth651f03a2020-08-27 16:15:11 -0500289 }
Matthew Barth651f03a2020-08-27 16:15:11 -0500290 }
291}
292
Matthew Bartha0dd1352021-03-09 11:10:49 -0600293bool Zone::isPersisted(const std::string& intf, const std::string& prop)
294{
295 auto it = _propsPersisted.find(intf);
296 if (it == _propsPersisted.end())
297 {
298 return false;
299 }
300
301 return std::any_of(it->second.begin(), it->second.end(),
302 [&prop](const auto& p) { return prop == p; });
303}
304
305void Zone::saveCurrentMode()
306{
307 fs::path path{CONTROL_PERSIST_ROOT_PATH};
308 // Append zone name and property description
309 path /= getName();
310 path /= "CurrentMode";
311 std::ofstream ofs(path.c_str(), std::ios::binary);
312 cereal::JSONOutputArchive oArch(ofs);
313 oArch(ThermalObject::current());
314}
315
Matthew Barth651f03a2020-08-27 16:15:11 -0500316/**
Matthew Barth216229c2020-09-24 13:47:33 -0500317 * Properties of interfaces supported by the zone configuration that return
Matthew Barthb584d812021-03-11 15:55:04 -0600318 * a handler function that sets the zone's property value(s) and persist state.
Matthew Barth651f03a2020-08-27 16:15:11 -0500319 */
320namespace zone::property
321{
Matthew Barthb584d812021-03-11 15:55:04 -0600322// Get a set property handler function for the configured values of the
323// "Supported" property
324std::function<void(Zone*)> supported(const json& jsonObj, bool persist)
Matthew Barth651f03a2020-08-27 16:15:11 -0500325{
326 std::vector<std::string> values;
Matthew Barth216229c2020-09-24 13:47:33 -0500327 if (!jsonObj.contains("values"))
Matthew Barth651f03a2020-08-27 16:15:11 -0500328 {
Matthew Barth216229c2020-09-24 13:47:33 -0500329 log<level::ERR>(
330 "No 'values' found for \"Supported\" property, using an empty list",
331 entry("JSON=%s", jsonObj.dump().c_str()));
332 }
333 else
334 {
335 for (const auto& value : jsonObj["values"])
336 {
337 if (!value.contains("value"))
338 {
339 log<level::ERR>("No 'value' found for \"Supported\" property "
340 "entry, skipping",
341 entry("JSON=%s", value.dump().c_str()));
342 }
343 else
344 {
345 values.emplace_back(value["value"].get<std::string>());
346 }
347 }
Matthew Barth651f03a2020-08-27 16:15:11 -0500348 }
349
Matthew Barthb584d812021-03-11 15:55:04 -0600350 return Zone::setProperty<std::vector<std::string>>(
351 Zone::thermModeIntf, Zone::supportedProp, &Zone::supported,
352 std::move(values), persist);
Matthew Barth651f03a2020-08-27 16:15:11 -0500353}
354
Matthew Barthb584d812021-03-11 15:55:04 -0600355// Get a set property handler function for a configured value of the "Current"
Matthew Barth216229c2020-09-24 13:47:33 -0500356// property
Matthew Barthb584d812021-03-11 15:55:04 -0600357std::function<void(Zone*)> current(const json& jsonObj, bool persist)
Matthew Barth651f03a2020-08-27 16:15:11 -0500358{
Matthew Barth216229c2020-09-24 13:47:33 -0500359 // Use default value for "Current" property if no "value" entry given
360 if (!jsonObj.contains("value"))
361 {
Matthew Barthb584d812021-03-11 15:55:04 -0600362 log<level::INFO>("No 'value' found for \"Current\" property, "
363 "using default",
364 entry("JSON=%s", jsonObj.dump().c_str()));
365 // Set persist state of property
366 return Zone::setPropertyPersist(Zone::thermModeIntf, Zone::currentProp,
367 persist);
Matthew Barth216229c2020-09-24 13:47:33 -0500368 }
369
Matthew Barthb584d812021-03-11 15:55:04 -0600370 return Zone::setProperty<std::string>(
371 Zone::thermModeIntf, Zone::currentProp, &Zone::current,
372 jsonObj["value"].get<std::string>(), persist);
Matthew Barth651f03a2020-08-27 16:15:11 -0500373}
Matthew Barthb584d812021-03-11 15:55:04 -0600374
Matthew Barth651f03a2020-08-27 16:15:11 -0500375} // namespace zone::property
376
Matthew Barth4f0d3b72020-08-27 14:32:15 -0500377} // namespace phosphor::fan::control::json