blob: 60547218fe3171a86021779f76e82b0f8610e598 [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),
54 _incDelay(0), _floor(0), _target(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{
100 // TODO Add from `requestSpeedIncrease` method in YAML zone object
101}
102
Matthew Bartha0dd1352021-03-09 11:10:49 -0600103void Zone::setPersisted(const std::string& intf, const std::string& prop)
104{
105 if (std::find_if(_propsPersisted[intf].begin(), _propsPersisted[intf].end(),
106 [&prop](const auto& p) { return prop == p; }) !=
107 _propsPersisted[intf].end())
108 {
109 _propsPersisted[intf].emplace_back(prop);
110 }
111}
112
113std::string Zone::current(std::string value)
114{
115 auto current = ThermalObject::current();
116 std::transform(value.begin(), value.end(), value.begin(), toupper);
117
118 auto supported = ThermalObject::supported();
119 auto isSupported =
120 std::any_of(supported.begin(), supported.end(), [&value](auto& s) {
121 std::transform(s.begin(), s.end(), s.begin(), toupper);
122 return value == s;
123 });
124
125 if (value != current && isSupported)
126 {
127 current = ThermalObject::current(value);
128 if (isPersisted("xyz.openbmc_project.Control.ThermalMode", "Current"))
129 {
130 saveCurrentMode();
131 }
132 // TODO Trigger event(s) for current mode property change
133 // auto eData =
134 // _objects[_path]["xyz.openbmc_project.Control.ThermalMode"]
135 // ["Current"];
136 // if (eData != nullptr)
137 // {
138 // sdbusplus::message::message nullMsg{nullptr};
139 // handleEvent(nullMsg, eData);
140 // }
141 }
142
143 return current;
144}
145
Matthew Barthe47c9582021-03-09 14:24:02 -0600146void Zone::setDefaultCeiling(const json& jsonObj)
Matthew Barth4f0d3b72020-08-27 14:32:15 -0500147{
148 if (!jsonObj.contains("full_speed"))
149 {
150 log<level::ERR>("Missing required zone's full speed",
151 entry("JSON=%s", jsonObj.dump().c_str()));
152 throw std::runtime_error("Missing required zone's full speed");
153 }
Matthew Barthe47c9582021-03-09 14:24:02 -0600154 _defaultCeiling = jsonObj["full_speed"].get<uint64_t>();
Matthew Barth12cb1252021-03-08 16:47:30 -0600155 // Start with the current target set as the default
Matthew Barthe47c9582021-03-09 14:24:02 -0600156 _target = _defaultCeiling;
Matthew Barth4f0d3b72020-08-27 14:32:15 -0500157}
158
159void Zone::setDefaultFloor(const json& jsonObj)
160{
161 if (!jsonObj.contains("default_floor"))
162 {
Matthew Barthe47c9582021-03-09 14:24:02 -0600163 log<level::ERR>("Missing required zone's default floor",
Matthew Barth4f0d3b72020-08-27 14:32:15 -0500164 entry("JSON=%s", jsonObj.dump().c_str()));
Matthew Barthe47c9582021-03-09 14:24:02 -0600165 throw std::runtime_error("Missing required zone's default floor");
Matthew Barth4f0d3b72020-08-27 14:32:15 -0500166 }
167 _defaultFloor = jsonObj["default_floor"].get<uint64_t>();
Matthew Barth12cb1252021-03-08 16:47:30 -0600168 // Start with the current floor set as the default
169 _floor = _defaultFloor;
Matthew Barth4f0d3b72020-08-27 14:32:15 -0500170}
171
172void Zone::setDecInterval(const json& jsonObj)
173{
174 if (!jsonObj.contains("decrease_interval"))
175 {
176 log<level::ERR>("Missing required zone's decrease interval",
177 entry("JSON=%s", jsonObj.dump().c_str()));
178 throw std::runtime_error("Missing required zone's decrease interval");
179 }
180 _decInterval = jsonObj["decrease_interval"].get<uint64_t>();
181}
182
Matthew Barth651f03a2020-08-27 16:15:11 -0500183void Zone::setInterfaces(const json& jsonObj)
184{
185 for (const auto& interface : jsonObj["interfaces"])
186 {
187 if (!interface.contains("name") || !interface.contains("properties"))
188 {
189 log<level::ERR>("Missing required zone interface attributes",
190 entry("JSON=%s", interface.dump().c_str()));
191 throw std::runtime_error(
192 "Missing required zone interface attributes");
193 }
Matthew Barth216229c2020-09-24 13:47:33 -0500194 auto propFuncs =
195 _intfPropHandlers.find(interface["name"].get<std::string>());
196 if (propFuncs == _intfPropHandlers.end())
197 {
198 // Construct list of available configurable interfaces
199 auto intfs = std::accumulate(
200 std::next(_intfPropHandlers.begin()), _intfPropHandlers.end(),
201 _intfPropHandlers.begin()->first, [](auto list, auto intf) {
202 return std::move(list) + ", " + intf.first;
203 });
204 log<level::ERR>("Configured interface not available",
205 entry("JSON=%s", interface.dump().c_str()),
206 entry("AVAILABLE_INTFS=%s", intfs.c_str()));
207 throw std::runtime_error("Configured interface not available");
208 }
209
Matthew Barth651f03a2020-08-27 16:15:11 -0500210 for (const auto& property : interface["properties"])
211 {
212 if (!property.contains("name"))
213 {
214 log<level::ERR>(
215 "Missing required interface property attributes",
216 entry("JSON=%s", property.dump().c_str()));
217 throw std::runtime_error(
218 "Missing required interface property attributes");
219 }
220 // Attribute "persist" is optional, defaults to `false`
221 auto persist = false;
222 if (property.contains("persist"))
223 {
224 persist = property["persist"].get<bool>();
225 }
226 // Property name from JSON must exactly match supported
227 // index names to functions in property namespace
Matthew Barth216229c2020-09-24 13:47:33 -0500228 auto propFunc =
229 propFuncs->second.find(property["name"].get<std::string>());
230 if (propFunc == propFuncs->second.end())
Matthew Barth651f03a2020-08-27 16:15:11 -0500231 {
Matthew Barth216229c2020-09-24 13:47:33 -0500232 // Construct list of available configurable properties
Matthew Barth651f03a2020-08-27 16:15:11 -0500233 auto props = std::accumulate(
Matthew Barth216229c2020-09-24 13:47:33 -0500234 std::next(propFuncs->second.begin()),
235 propFuncs->second.end(), propFuncs->second.begin()->first,
236 [](auto list, auto prop) {
Matthew Barth651f03a2020-08-27 16:15:11 -0500237 return std::move(list) + ", " + prop.first;
238 });
Matthew Barth216229c2020-09-24 13:47:33 -0500239 log<level::ERR>("Configured property not available",
Matthew Barth651f03a2020-08-27 16:15:11 -0500240 entry("JSON=%s", property.dump().c_str()),
241 entry("AVAILABLE_PROPS=%s", props.c_str()));
242 throw std::runtime_error(
243 "Configured property function not available");
244 }
Matthew Barth216229c2020-09-24 13:47:33 -0500245 auto zHandler = propFunc->second(property, persist);
246 // Only add non-null zone handler functions
247 if (zHandler)
248 {
249 _zoneHandlers.emplace_back(zHandler);
250 }
Matthew Barth651f03a2020-08-27 16:15:11 -0500251 }
Matthew Barth651f03a2020-08-27 16:15:11 -0500252 }
253}
254
Matthew Bartha0dd1352021-03-09 11:10:49 -0600255bool Zone::isPersisted(const std::string& intf, const std::string& prop)
256{
257 auto it = _propsPersisted.find(intf);
258 if (it == _propsPersisted.end())
259 {
260 return false;
261 }
262
263 return std::any_of(it->second.begin(), it->second.end(),
264 [&prop](const auto& p) { return prop == p; });
265}
266
267void Zone::saveCurrentMode()
268{
269 fs::path path{CONTROL_PERSIST_ROOT_PATH};
270 // Append zone name and property description
271 path /= getName();
272 path /= "CurrentMode";
273 std::ofstream ofs(path.c_str(), std::ios::binary);
274 cereal::JSONOutputArchive oArch(ofs);
275 oArch(ThermalObject::current());
276}
277
Matthew Barth651f03a2020-08-27 16:15:11 -0500278/**
Matthew Barth216229c2020-09-24 13:47:33 -0500279 * Properties of interfaces supported by the zone configuration that return
280 * a ZoneHandler function that sets the zone's property value(s).
Matthew Barth651f03a2020-08-27 16:15:11 -0500281 */
282namespace zone::property
283{
Matthew Barth216229c2020-09-24 13:47:33 -0500284// Get a zone handler function for the configured values of the "Supported"
285// property
286ZoneHandler supported(const json& jsonObj, bool persist)
Matthew Barth651f03a2020-08-27 16:15:11 -0500287{
288 std::vector<std::string> values;
Matthew Barth216229c2020-09-24 13:47:33 -0500289 if (!jsonObj.contains("values"))
Matthew Barth651f03a2020-08-27 16:15:11 -0500290 {
Matthew Barth216229c2020-09-24 13:47:33 -0500291 log<level::ERR>(
292 "No 'values' found for \"Supported\" property, using an empty list",
293 entry("JSON=%s", jsonObj.dump().c_str()));
294 }
295 else
296 {
297 for (const auto& value : jsonObj["values"])
298 {
299 if (!value.contains("value"))
300 {
301 log<level::ERR>("No 'value' found for \"Supported\" property "
302 "entry, skipping",
303 entry("JSON=%s", value.dump().c_str()));
304 }
305 else
306 {
307 values.emplace_back(value["value"].get<std::string>());
308 }
309 }
Matthew Barth651f03a2020-08-27 16:15:11 -0500310 }
311
Matthew Bartha0dd1352021-03-09 11:10:49 -0600312 // TODO Use this zone object's extended `supported` method in the handler
Matthew Barth216229c2020-09-24 13:47:33 -0500313 return make_zoneHandler(handler::setZoneProperty<std::vector<std::string>>(
314 Zone::thermModeIntf, Zone::supportedProp, &control::Zone::supported,
315 std::move(values), persist));
Matthew Barth651f03a2020-08-27 16:15:11 -0500316}
317
Matthew Barth216229c2020-09-24 13:47:33 -0500318// Get a zone handler function for a configured value of the "Current"
319// property
320ZoneHandler current(const json& jsonObj, bool persist)
Matthew Barth651f03a2020-08-27 16:15:11 -0500321{
Matthew Barth216229c2020-09-24 13:47:33 -0500322 // Use default value for "Current" property if no "value" entry given
323 if (!jsonObj.contains("value"))
324 {
325 log<level::ERR>("No 'value' found for \"Current\" property, "
326 "using default",
327 entry("JSON=%s", jsonObj.dump().c_str()));
328 return {};
329 }
330
Matthew Bartha0dd1352021-03-09 11:10:49 -0600331 // TODO Use this zone object's `current` method in the handler
Matthew Barth216229c2020-09-24 13:47:33 -0500332 return make_zoneHandler(handler::setZoneProperty<std::string>(
333 Zone::thermModeIntf, Zone::currentProp, &control::Zone::current,
334 jsonObj["value"].get<std::string>(), persist));
Matthew Barth651f03a2020-08-27 16:15:11 -0500335}
336} // namespace zone::property
337
Matthew Barth4f0d3b72020-08-27 14:32:15 -0500338} // namespace phosphor::fan::control::json