blob: 62491948375ea140e646852f04cc7571fb041f10 [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>
30#include <filesystem>
31#include <fstream>
Matthew Barth651f03a2020-08-27 16:15:11 -050032#include <iterator>
33#include <map>
34#include <numeric>
Matthew Barth651f03a2020-08-27 16:15:11 -050035#include <utility>
Matthew Barth216229c2020-09-24 13:47:33 -050036#include <vector>
Matthew Barth651f03a2020-08-27 16:15:11 -050037
Matthew Barth4f0d3b72020-08-27 14:32:15 -050038namespace phosphor::fan::control::json
39{
40
41using json = nlohmann::json;
42using namespace phosphor::logging;
Matthew Bartha0dd1352021-03-09 11:10:49 -060043namespace fs = std::filesystem;
Matthew Barth4f0d3b72020-08-27 14:32:15 -050044
Matthew Barthb584d812021-03-11 15:55:04 -060045const std::map<std::string,
46 std::map<std::string, std::function<std::function<void(Zone*)>(
47 const json&, bool)>>>
Matthew Barth216229c2020-09-24 13:47:33 -050048 Zone::_intfPropHandlers = {{thermModeIntf,
49 {{supportedProp, zone::property::supported},
50 {currentProp, zone::property::current}}}};
Matthew Barth651f03a2020-08-27 16:15:11 -050051
Matthew Barth603ef162021-03-24 15:34:53 -050052Zone::Zone(const json& jsonObj, sdbusplus::bus::bus& bus,
53 const sdeventplus::Event& event, Manager* mgr) :
Matthew Bartha0dd1352021-03-09 11:10:49 -060054 ConfigBase(jsonObj),
55 ThermalObject(bus, (fs::path{CONTROL_OBJPATH} /= getName()).c_str(), true),
Matthew Barth603ef162021-03-24 15:34:53 -050056 _manager(mgr), _incDelay(0), _floor(0), _target(0), _incDelta(0),
57 _decDelta(0), _requestTargetBase(0), _isActive(true)
Matthew Barth4f0d3b72020-08-27 14:32:15 -050058{
Matthew Barthe47c9582021-03-09 14:24:02 -060059 // Increase delay is optional, defaults to 0
Matthew Barth4f0d3b72020-08-27 14:32:15 -050060 if (jsonObj.contains("increase_delay"))
61 {
62 _incDelay = jsonObj["increase_delay"].get<uint64_t>();
63 }
Matthew Barthe47c9582021-03-09 14:24:02 -060064 setDefaultCeiling(jsonObj);
Matthew Barth4f0d3b72020-08-27 14:32:15 -050065 setDefaultFloor(jsonObj);
66 setDecInterval(jsonObj);
Matthew Barth651f03a2020-08-27 16:15:11 -050067 // Setting properties on interfaces to be served are optional
68 if (jsonObj.contains("interfaces"))
69 {
70 setInterfaces(jsonObj);
71 }
Matthew Barth4f0d3b72020-08-27 14:32:15 -050072}
73
Matthew Barthde90fb42021-03-04 16:34:28 -060074void Zone::addFan(std::unique_ptr<Fan> fan)
75{
76 _fans.emplace_back(std::move(fan));
77}
78
Matthew Barth8ba715e2021-03-05 09:00:05 -060079void Zone::setTarget(uint64_t target)
80{
81 if (_isActive)
82 {
83 _target = target;
84 for (auto& fan : _fans)
85 {
86 fan->setTarget(_target);
87 }
88 }
89}
90
91void Zone::setActiveAllow(const std::string& ident, bool isActiveAllow)
92{
93 _active[ident] = isActiveAllow;
94 if (!isActiveAllow)
95 {
96 _isActive = false;
97 }
98 else
99 {
100 // Check all entries are set to allow active fan control
101 auto actPred = [](const auto& entry) { return entry.second; };
102 _isActive = std::all_of(_active.begin(), _active.end(), actPred);
103 }
104}
105
Matthew Barth12cb1252021-03-08 16:47:30 -0600106void Zone::setFloor(uint64_t target)
107{
108 // Check all entries are set to allow floor to be set
Matthew Barth8ba715e2021-03-05 09:00:05 -0600109 auto pred = [](const auto& entry) { return entry.second; };
Matthew Barth12cb1252021-03-08 16:47:30 -0600110 if (std::all_of(_floorChange.begin(), _floorChange.end(), pred))
111 {
112 _floor = target;
113 // Floor above target, update target to floor
114 if (_target < _floor)
115 {
116 requestIncrease(_floor - _target);
117 }
118 }
119}
120
121void Zone::requestIncrease(uint64_t targetDelta)
122{
Matthew Barth2b3253e2021-03-09 14:51:16 -0600123 // Only increase when delta is higher than the current increase delta for
124 // the zone and currently under ceiling
125 if (targetDelta > _incDelta && _target < _ceiling)
126 {
127 auto requestTarget = getRequestTargetBase();
128 requestTarget = (targetDelta - _incDelta) + requestTarget;
129 _incDelta = targetDelta;
130 // Target can not go above a current ceiling
131 if (requestTarget > _ceiling)
132 {
133 requestTarget = _ceiling;
134 }
Matthew Barth8ba715e2021-03-05 09:00:05 -0600135 setTarget(requestTarget);
136 // TODO // Restart timer countdown for fan speed increase
Matthew Barth2b3253e2021-03-09 14:51:16 -0600137 // _incTimer.restartOnce(_incDelay);
138 }
Matthew Barth12cb1252021-03-08 16:47:30 -0600139}
140
Matthew Barth45c44ea2021-03-03 13:16:14 -0600141void Zone::requestDecrease(uint64_t targetDelta)
142{
143 // Only decrease the lowest target delta requested
144 if (_decDelta == 0 || targetDelta < _decDelta)
145 {
146 _decDelta = targetDelta;
147 }
148}
149
Matthew Bartha0dd1352021-03-09 11:10:49 -0600150void Zone::setPersisted(const std::string& intf, const std::string& prop)
151{
152 if (std::find_if(_propsPersisted[intf].begin(), _propsPersisted[intf].end(),
153 [&prop](const auto& p) { return prop == p; }) !=
154 _propsPersisted[intf].end())
155 {
156 _propsPersisted[intf].emplace_back(prop);
157 }
158}
159
160std::string Zone::current(std::string value)
161{
162 auto current = ThermalObject::current();
163 std::transform(value.begin(), value.end(), value.begin(), toupper);
164
165 auto supported = ThermalObject::supported();
166 auto isSupported =
167 std::any_of(supported.begin(), supported.end(), [&value](auto& s) {
168 std::transform(s.begin(), s.end(), s.begin(), toupper);
169 return value == s;
170 });
171
172 if (value != current && isSupported)
173 {
174 current = ThermalObject::current(value);
175 if (isPersisted("xyz.openbmc_project.Control.ThermalMode", "Current"))
176 {
177 saveCurrentMode();
178 }
179 // TODO Trigger event(s) for current mode property change
180 // auto eData =
181 // _objects[_path]["xyz.openbmc_project.Control.ThermalMode"]
182 // ["Current"];
183 // if (eData != nullptr)
184 // {
185 // sdbusplus::message::message nullMsg{nullptr};
186 // handleEvent(nullMsg, eData);
187 // }
188 }
189
190 return current;
191}
192
Matthew Barthe47c9582021-03-09 14:24:02 -0600193void Zone::setDefaultCeiling(const json& jsonObj)
Matthew Barth4f0d3b72020-08-27 14:32:15 -0500194{
195 if (!jsonObj.contains("full_speed"))
196 {
197 log<level::ERR>("Missing required zone's full speed",
198 entry("JSON=%s", jsonObj.dump().c_str()));
199 throw std::runtime_error("Missing required zone's full speed");
200 }
Matthew Barthe47c9582021-03-09 14:24:02 -0600201 _defaultCeiling = jsonObj["full_speed"].get<uint64_t>();
Matthew Barth12cb1252021-03-08 16:47:30 -0600202 // Start with the current target set as the default
Matthew Barthe47c9582021-03-09 14:24:02 -0600203 _target = _defaultCeiling;
Matthew Barth2b3253e2021-03-09 14:51:16 -0600204 // Start with the current ceiling set as the default
205 _ceiling = _defaultCeiling;
Matthew Barth4f0d3b72020-08-27 14:32:15 -0500206}
207
208void Zone::setDefaultFloor(const json& jsonObj)
209{
210 if (!jsonObj.contains("default_floor"))
211 {
Matthew Barthe47c9582021-03-09 14:24:02 -0600212 log<level::ERR>("Missing required zone's default floor",
Matthew Barth4f0d3b72020-08-27 14:32:15 -0500213 entry("JSON=%s", jsonObj.dump().c_str()));
Matthew Barthe47c9582021-03-09 14:24:02 -0600214 throw std::runtime_error("Missing required zone's default floor");
Matthew Barth4f0d3b72020-08-27 14:32:15 -0500215 }
216 _defaultFloor = jsonObj["default_floor"].get<uint64_t>();
Matthew Barth12cb1252021-03-08 16:47:30 -0600217 // Start with the current floor set as the default
218 _floor = _defaultFloor;
Matthew Barth4f0d3b72020-08-27 14:32:15 -0500219}
220
221void Zone::setDecInterval(const json& jsonObj)
222{
223 if (!jsonObj.contains("decrease_interval"))
224 {
225 log<level::ERR>("Missing required zone's decrease interval",
226 entry("JSON=%s", jsonObj.dump().c_str()));
227 throw std::runtime_error("Missing required zone's decrease interval");
228 }
229 _decInterval = jsonObj["decrease_interval"].get<uint64_t>();
230}
231
Matthew Barth651f03a2020-08-27 16:15:11 -0500232void Zone::setInterfaces(const json& jsonObj)
233{
234 for (const auto& interface : jsonObj["interfaces"])
235 {
236 if (!interface.contains("name") || !interface.contains("properties"))
237 {
238 log<level::ERR>("Missing required zone interface attributes",
239 entry("JSON=%s", interface.dump().c_str()));
240 throw std::runtime_error(
241 "Missing required zone interface attributes");
242 }
Matthew Barth216229c2020-09-24 13:47:33 -0500243 auto propFuncs =
244 _intfPropHandlers.find(interface["name"].get<std::string>());
245 if (propFuncs == _intfPropHandlers.end())
246 {
247 // Construct list of available configurable interfaces
248 auto intfs = std::accumulate(
249 std::next(_intfPropHandlers.begin()), _intfPropHandlers.end(),
250 _intfPropHandlers.begin()->first, [](auto list, auto intf) {
251 return std::move(list) + ", " + intf.first;
252 });
253 log<level::ERR>("Configured interface not available",
254 entry("JSON=%s", interface.dump().c_str()),
255 entry("AVAILABLE_INTFS=%s", intfs.c_str()));
256 throw std::runtime_error("Configured interface not available");
257 }
258
Matthew Barth651f03a2020-08-27 16:15:11 -0500259 for (const auto& property : interface["properties"])
260 {
261 if (!property.contains("name"))
262 {
263 log<level::ERR>(
264 "Missing required interface property attributes",
265 entry("JSON=%s", property.dump().c_str()));
266 throw std::runtime_error(
267 "Missing required interface property attributes");
268 }
269 // Attribute "persist" is optional, defaults to `false`
270 auto persist = false;
271 if (property.contains("persist"))
272 {
273 persist = property["persist"].get<bool>();
274 }
275 // Property name from JSON must exactly match supported
276 // index names to functions in property namespace
Matthew Barth216229c2020-09-24 13:47:33 -0500277 auto propFunc =
278 propFuncs->second.find(property["name"].get<std::string>());
279 if (propFunc == propFuncs->second.end())
Matthew Barth651f03a2020-08-27 16:15:11 -0500280 {
Matthew Barth216229c2020-09-24 13:47:33 -0500281 // Construct list of available configurable properties
Matthew Barth651f03a2020-08-27 16:15:11 -0500282 auto props = std::accumulate(
Matthew Barth216229c2020-09-24 13:47:33 -0500283 std::next(propFuncs->second.begin()),
284 propFuncs->second.end(), propFuncs->second.begin()->first,
285 [](auto list, auto prop) {
Matthew Barth651f03a2020-08-27 16:15:11 -0500286 return std::move(list) + ", " + prop.first;
287 });
Matthew Barth216229c2020-09-24 13:47:33 -0500288 log<level::ERR>("Configured property not available",
Matthew Barth651f03a2020-08-27 16:15:11 -0500289 entry("JSON=%s", property.dump().c_str()),
290 entry("AVAILABLE_PROPS=%s", props.c_str()));
291 throw std::runtime_error(
292 "Configured property function not available");
293 }
Matthew Barthb584d812021-03-11 15:55:04 -0600294 auto propHandler = propFunc->second(property, persist);
295 // Only call non-null set property handler functions
296 if (propHandler)
297 {
298 propHandler(this);
299 }
Matthew Barth651f03a2020-08-27 16:15:11 -0500300 }
Matthew Barth651f03a2020-08-27 16:15:11 -0500301 }
302}
303
Matthew Bartha0dd1352021-03-09 11:10:49 -0600304bool Zone::isPersisted(const std::string& intf, const std::string& prop)
305{
306 auto it = _propsPersisted.find(intf);
307 if (it == _propsPersisted.end())
308 {
309 return false;
310 }
311
312 return std::any_of(it->second.begin(), it->second.end(),
313 [&prop](const auto& p) { return prop == p; });
314}
315
316void Zone::saveCurrentMode()
317{
318 fs::path path{CONTROL_PERSIST_ROOT_PATH};
319 // Append zone name and property description
320 path /= getName();
321 path /= "CurrentMode";
322 std::ofstream ofs(path.c_str(), std::ios::binary);
323 cereal::JSONOutputArchive oArch(ofs);
324 oArch(ThermalObject::current());
325}
326
Matthew Barth651f03a2020-08-27 16:15:11 -0500327/**
Matthew Barth216229c2020-09-24 13:47:33 -0500328 * Properties of interfaces supported by the zone configuration that return
Matthew Barthb584d812021-03-11 15:55:04 -0600329 * a handler function that sets the zone's property value(s) and persist state.
Matthew Barth651f03a2020-08-27 16:15:11 -0500330 */
331namespace zone::property
332{
Matthew Barthb584d812021-03-11 15:55:04 -0600333// Get a set property handler function for the configured values of the
334// "Supported" property
335std::function<void(Zone*)> supported(const json& jsonObj, bool persist)
Matthew Barth651f03a2020-08-27 16:15:11 -0500336{
337 std::vector<std::string> values;
Matthew Barth216229c2020-09-24 13:47:33 -0500338 if (!jsonObj.contains("values"))
Matthew Barth651f03a2020-08-27 16:15:11 -0500339 {
Matthew Barth216229c2020-09-24 13:47:33 -0500340 log<level::ERR>(
341 "No 'values' found for \"Supported\" property, using an empty list",
342 entry("JSON=%s", jsonObj.dump().c_str()));
343 }
344 else
345 {
346 for (const auto& value : jsonObj["values"])
347 {
348 if (!value.contains("value"))
349 {
350 log<level::ERR>("No 'value' found for \"Supported\" property "
351 "entry, skipping",
352 entry("JSON=%s", value.dump().c_str()));
353 }
354 else
355 {
356 values.emplace_back(value["value"].get<std::string>());
357 }
358 }
Matthew Barth651f03a2020-08-27 16:15:11 -0500359 }
360
Matthew Barthb584d812021-03-11 15:55:04 -0600361 return Zone::setProperty<std::vector<std::string>>(
362 Zone::thermModeIntf, Zone::supportedProp, &Zone::supported,
363 std::move(values), persist);
Matthew Barth651f03a2020-08-27 16:15:11 -0500364}
365
Matthew Barthb584d812021-03-11 15:55:04 -0600366// Get a set property handler function for a configured value of the "Current"
Matthew Barth216229c2020-09-24 13:47:33 -0500367// property
Matthew Barthb584d812021-03-11 15:55:04 -0600368std::function<void(Zone*)> current(const json& jsonObj, bool persist)
Matthew Barth651f03a2020-08-27 16:15:11 -0500369{
Matthew Barth216229c2020-09-24 13:47:33 -0500370 // Use default value for "Current" property if no "value" entry given
371 if (!jsonObj.contains("value"))
372 {
Matthew Barthb584d812021-03-11 15:55:04 -0600373 log<level::INFO>("No 'value' found for \"Current\" property, "
374 "using default",
375 entry("JSON=%s", jsonObj.dump().c_str()));
376 // Set persist state of property
377 return Zone::setPropertyPersist(Zone::thermModeIntf, Zone::currentProp,
378 persist);
Matthew Barth216229c2020-09-24 13:47:33 -0500379 }
380
Matthew Barthb584d812021-03-11 15:55:04 -0600381 return Zone::setProperty<std::string>(
382 Zone::thermModeIntf, Zone::currentProp, &Zone::current,
383 jsonObj["value"].get<std::string>(), persist);
Matthew Barth651f03a2020-08-27 16:15:11 -0500384}
Matthew Barthb584d812021-03-11 15:55:04 -0600385
Matthew Barth651f03a2020-08-27 16:15:11 -0500386} // namespace zone::property
387
Matthew Barth4f0d3b72020-08-27 14:32:15 -0500388} // namespace phosphor::fan::control::json