blob: 3c3f0b307aebf68dc66a673eeb531b1b2692dded [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#include "functor.hpp"
22#include "handlers.hpp"
Matthew Barth216229c2020-09-24 13:47:33 -050023
Matthew Bartha0dd1352021-03-09 11:10:49 -060024#include <cereal/archives/json.hpp>
25#include <cereal/cereal.hpp>
Matthew Barth4f0d3b72020-08-27 14:32:15 -050026#include <nlohmann/json.hpp>
27#include <phosphor-logging/log.hpp>
Matthew Barthacd737c2021-03-04 11:04:01 -060028#include <sdbusplus/bus.hpp>
Matthew Barth4f0d3b72020-08-27 14:32:15 -050029
Matthew Bartha0dd1352021-03-09 11:10:49 -060030#include <algorithm>
31#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 Barth216229c2020-09-24 13:47:33 -050046const std::map<std::string, std::map<std::string, propHandler>>
47 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 Barth2b3253e2021-03-09 14:51:16 -060054 _incDelay(0), _floor(0), _target(0), _incDelta(0), _requestTargetBase(0)
Matthew Barth4f0d3b72020-08-27 14:32:15 -050055{
56 if (jsonObj.contains("profiles"))
57 {
58 for (const auto& profile : jsonObj["profiles"])
59 {
60 _profiles.emplace_back(profile.get<std::string>());
61 }
62 }
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 {
66 _incDelay = jsonObj["increase_delay"].get<uint64_t>();
67 }
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 Barth4f0d3b72020-08-27 14:32:15 -050076}
77
Matthew Barthde90fb42021-03-04 16:34:28 -060078void Zone::addFan(std::unique_ptr<Fan> fan)
79{
80 _fans.emplace_back(std::move(fan));
81}
82
Matthew Barth12cb1252021-03-08 16:47:30 -060083void Zone::setFloor(uint64_t target)
84{
85 // Check all entries are set to allow floor to be set
86 auto pred = [](auto const& entry) { return entry.second; };
87 if (std::all_of(_floorChange.begin(), _floorChange.end(), pred))
88 {
89 _floor = target;
90 // Floor above target, update target to floor
91 if (_target < _floor)
92 {
93 requestIncrease(_floor - _target);
94 }
95 }
96}
97
98void Zone::requestIncrease(uint64_t targetDelta)
99{
Matthew Barth2b3253e2021-03-09 14:51:16 -0600100 // Only increase when delta is higher than the current increase delta for
101 // the zone and currently under ceiling
102 if (targetDelta > _incDelta && _target < _ceiling)
103 {
104 auto requestTarget = getRequestTargetBase();
105 requestTarget = (targetDelta - _incDelta) + requestTarget;
106 _incDelta = targetDelta;
107 // Target can not go above a current ceiling
108 if (requestTarget > _ceiling)
109 {
110 requestTarget = _ceiling;
111 }
112 // TODO Set target and handle increase timer
113 // setTarget(requestTarget);
114 // // Restart timer countdown for fan speed increase
115 // _incTimer.restartOnce(_incDelay);
116 }
Matthew Barth12cb1252021-03-08 16:47:30 -0600117}
118
Matthew Bartha0dd1352021-03-09 11:10:49 -0600119void Zone::setPersisted(const std::string& intf, const std::string& prop)
120{
121 if (std::find_if(_propsPersisted[intf].begin(), _propsPersisted[intf].end(),
122 [&prop](const auto& p) { return prop == p; }) !=
123 _propsPersisted[intf].end())
124 {
125 _propsPersisted[intf].emplace_back(prop);
126 }
127}
128
129std::string Zone::current(std::string value)
130{
131 auto current = ThermalObject::current();
132 std::transform(value.begin(), value.end(), value.begin(), toupper);
133
134 auto supported = ThermalObject::supported();
135 auto isSupported =
136 std::any_of(supported.begin(), supported.end(), [&value](auto& s) {
137 std::transform(s.begin(), s.end(), s.begin(), toupper);
138 return value == s;
139 });
140
141 if (value != current && isSupported)
142 {
143 current = ThermalObject::current(value);
144 if (isPersisted("xyz.openbmc_project.Control.ThermalMode", "Current"))
145 {
146 saveCurrentMode();
147 }
148 // TODO Trigger event(s) for current mode property change
149 // auto eData =
150 // _objects[_path]["xyz.openbmc_project.Control.ThermalMode"]
151 // ["Current"];
152 // if (eData != nullptr)
153 // {
154 // sdbusplus::message::message nullMsg{nullptr};
155 // handleEvent(nullMsg, eData);
156 // }
157 }
158
159 return current;
160}
161
Matthew Barthe47c9582021-03-09 14:24:02 -0600162void Zone::setDefaultCeiling(const json& jsonObj)
Matthew Barth4f0d3b72020-08-27 14:32:15 -0500163{
164 if (!jsonObj.contains("full_speed"))
165 {
166 log<level::ERR>("Missing required zone's full speed",
167 entry("JSON=%s", jsonObj.dump().c_str()));
168 throw std::runtime_error("Missing required zone's full speed");
169 }
Matthew Barthe47c9582021-03-09 14:24:02 -0600170 _defaultCeiling = jsonObj["full_speed"].get<uint64_t>();
Matthew Barth12cb1252021-03-08 16:47:30 -0600171 // Start with the current target set as the default
Matthew Barthe47c9582021-03-09 14:24:02 -0600172 _target = _defaultCeiling;
Matthew Barth2b3253e2021-03-09 14:51:16 -0600173 // Start with the current ceiling set as the default
174 _ceiling = _defaultCeiling;
Matthew Barth4f0d3b72020-08-27 14:32:15 -0500175}
176
177void Zone::setDefaultFloor(const json& jsonObj)
178{
179 if (!jsonObj.contains("default_floor"))
180 {
Matthew Barthe47c9582021-03-09 14:24:02 -0600181 log<level::ERR>("Missing required zone's default floor",
Matthew Barth4f0d3b72020-08-27 14:32:15 -0500182 entry("JSON=%s", jsonObj.dump().c_str()));
Matthew Barthe47c9582021-03-09 14:24:02 -0600183 throw std::runtime_error("Missing required zone's default floor");
Matthew Barth4f0d3b72020-08-27 14:32:15 -0500184 }
185 _defaultFloor = jsonObj["default_floor"].get<uint64_t>();
Matthew Barth12cb1252021-03-08 16:47:30 -0600186 // Start with the current floor set as the default
187 _floor = _defaultFloor;
Matthew Barth4f0d3b72020-08-27 14:32:15 -0500188}
189
190void Zone::setDecInterval(const json& jsonObj)
191{
192 if (!jsonObj.contains("decrease_interval"))
193 {
194 log<level::ERR>("Missing required zone's decrease interval",
195 entry("JSON=%s", jsonObj.dump().c_str()));
196 throw std::runtime_error("Missing required zone's decrease interval");
197 }
198 _decInterval = jsonObj["decrease_interval"].get<uint64_t>();
199}
200
Matthew Barth651f03a2020-08-27 16:15:11 -0500201void Zone::setInterfaces(const json& jsonObj)
202{
203 for (const auto& interface : jsonObj["interfaces"])
204 {
205 if (!interface.contains("name") || !interface.contains("properties"))
206 {
207 log<level::ERR>("Missing required zone interface attributes",
208 entry("JSON=%s", interface.dump().c_str()));
209 throw std::runtime_error(
210 "Missing required zone interface attributes");
211 }
Matthew Barth216229c2020-09-24 13:47:33 -0500212 auto propFuncs =
213 _intfPropHandlers.find(interface["name"].get<std::string>());
214 if (propFuncs == _intfPropHandlers.end())
215 {
216 // Construct list of available configurable interfaces
217 auto intfs = std::accumulate(
218 std::next(_intfPropHandlers.begin()), _intfPropHandlers.end(),
219 _intfPropHandlers.begin()->first, [](auto list, auto intf) {
220 return std::move(list) + ", " + intf.first;
221 });
222 log<level::ERR>("Configured interface not available",
223 entry("JSON=%s", interface.dump().c_str()),
224 entry("AVAILABLE_INTFS=%s", intfs.c_str()));
225 throw std::runtime_error("Configured interface not available");
226 }
227
Matthew Barth651f03a2020-08-27 16:15:11 -0500228 for (const auto& property : interface["properties"])
229 {
230 if (!property.contains("name"))
231 {
232 log<level::ERR>(
233 "Missing required interface property attributes",
234 entry("JSON=%s", property.dump().c_str()));
235 throw std::runtime_error(
236 "Missing required interface property attributes");
237 }
238 // Attribute "persist" is optional, defaults to `false`
239 auto persist = false;
240 if (property.contains("persist"))
241 {
242 persist = property["persist"].get<bool>();
243 }
244 // Property name from JSON must exactly match supported
245 // index names to functions in property namespace
Matthew Barth216229c2020-09-24 13:47:33 -0500246 auto propFunc =
247 propFuncs->second.find(property["name"].get<std::string>());
248 if (propFunc == propFuncs->second.end())
Matthew Barth651f03a2020-08-27 16:15:11 -0500249 {
Matthew Barth216229c2020-09-24 13:47:33 -0500250 // Construct list of available configurable properties
Matthew Barth651f03a2020-08-27 16:15:11 -0500251 auto props = std::accumulate(
Matthew Barth216229c2020-09-24 13:47:33 -0500252 std::next(propFuncs->second.begin()),
253 propFuncs->second.end(), propFuncs->second.begin()->first,
254 [](auto list, auto prop) {
Matthew Barth651f03a2020-08-27 16:15:11 -0500255 return std::move(list) + ", " + prop.first;
256 });
Matthew Barth216229c2020-09-24 13:47:33 -0500257 log<level::ERR>("Configured property not available",
Matthew Barth651f03a2020-08-27 16:15:11 -0500258 entry("JSON=%s", property.dump().c_str()),
259 entry("AVAILABLE_PROPS=%s", props.c_str()));
260 throw std::runtime_error(
261 "Configured property function not available");
262 }
Matthew Barth216229c2020-09-24 13:47:33 -0500263 auto zHandler = propFunc->second(property, persist);
264 // Only add non-null zone handler functions
265 if (zHandler)
266 {
267 _zoneHandlers.emplace_back(zHandler);
268 }
Matthew Barth651f03a2020-08-27 16:15:11 -0500269 }
Matthew Barth651f03a2020-08-27 16:15:11 -0500270 }
271}
272
Matthew Bartha0dd1352021-03-09 11:10:49 -0600273bool Zone::isPersisted(const std::string& intf, const std::string& prop)
274{
275 auto it = _propsPersisted.find(intf);
276 if (it == _propsPersisted.end())
277 {
278 return false;
279 }
280
281 return std::any_of(it->second.begin(), it->second.end(),
282 [&prop](const auto& p) { return prop == p; });
283}
284
285void Zone::saveCurrentMode()
286{
287 fs::path path{CONTROL_PERSIST_ROOT_PATH};
288 // Append zone name and property description
289 path /= getName();
290 path /= "CurrentMode";
291 std::ofstream ofs(path.c_str(), std::ios::binary);
292 cereal::JSONOutputArchive oArch(ofs);
293 oArch(ThermalObject::current());
294}
295
Matthew Barth651f03a2020-08-27 16:15:11 -0500296/**
Matthew Barth216229c2020-09-24 13:47:33 -0500297 * Properties of interfaces supported by the zone configuration that return
298 * a ZoneHandler function that sets the zone's property value(s).
Matthew Barth651f03a2020-08-27 16:15:11 -0500299 */
300namespace zone::property
301{
Matthew Barth216229c2020-09-24 13:47:33 -0500302// Get a zone handler function for the configured values of the "Supported"
303// property
304ZoneHandler supported(const json& jsonObj, bool persist)
Matthew Barth651f03a2020-08-27 16:15:11 -0500305{
306 std::vector<std::string> values;
Matthew Barth216229c2020-09-24 13:47:33 -0500307 if (!jsonObj.contains("values"))
Matthew Barth651f03a2020-08-27 16:15:11 -0500308 {
Matthew Barth216229c2020-09-24 13:47:33 -0500309 log<level::ERR>(
310 "No 'values' found for \"Supported\" property, using an empty list",
311 entry("JSON=%s", jsonObj.dump().c_str()));
312 }
313 else
314 {
315 for (const auto& value : jsonObj["values"])
316 {
317 if (!value.contains("value"))
318 {
319 log<level::ERR>("No 'value' found for \"Supported\" property "
320 "entry, skipping",
321 entry("JSON=%s", value.dump().c_str()));
322 }
323 else
324 {
325 values.emplace_back(value["value"].get<std::string>());
326 }
327 }
Matthew Barth651f03a2020-08-27 16:15:11 -0500328 }
329
Matthew Bartha0dd1352021-03-09 11:10:49 -0600330 // TODO Use this zone object's extended `supported` method in the handler
Matthew Barth216229c2020-09-24 13:47:33 -0500331 return make_zoneHandler(handler::setZoneProperty<std::vector<std::string>>(
332 Zone::thermModeIntf, Zone::supportedProp, &control::Zone::supported,
333 std::move(values), persist));
Matthew Barth651f03a2020-08-27 16:15:11 -0500334}
335
Matthew Barth216229c2020-09-24 13:47:33 -0500336// Get a zone handler function for a configured value of the "Current"
337// property
338ZoneHandler current(const json& jsonObj, bool persist)
Matthew Barth651f03a2020-08-27 16:15:11 -0500339{
Matthew Barth216229c2020-09-24 13:47:33 -0500340 // Use default value for "Current" property if no "value" entry given
341 if (!jsonObj.contains("value"))
342 {
343 log<level::ERR>("No 'value' found for \"Current\" property, "
344 "using default",
345 entry("JSON=%s", jsonObj.dump().c_str()));
346 return {};
347 }
348
Matthew Bartha0dd1352021-03-09 11:10:49 -0600349 // TODO Use this zone object's `current` method in the handler
Matthew Barth216229c2020-09-24 13:47:33 -0500350 return make_zoneHandler(handler::setZoneProperty<std::string>(
351 Zone::thermModeIntf, Zone::currentProp, &control::Zone::current,
352 jsonObj["value"].get<std::string>(), persist));
Matthew Barth651f03a2020-08-27 16:15:11 -0500353}
354} // namespace zone::property
355
Matthew Barth4f0d3b72020-08-27 14:32:15 -0500356} // namespace phosphor::fan::control::json