xref: /openbmc/phosphor-fan-presence/control/json/fan.cpp (revision 1b3bcc3240277b0dcfd8c65e693f9c8f7024c851)
1  /**
2   * Copyright © 2022 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   */
16  #include "fan.hpp"
17  
18  #include "sdbusplus.hpp"
19  
20  #include <nlohmann/json.hpp>
21  #include <phosphor-logging/log.hpp>
22  #include <sdbusplus/bus.hpp>
23  
24  #include <format>
25  
26  namespace phosphor::fan::control::json
27  {
28  
29  using json = nlohmann::json;
30  using namespace phosphor::logging;
31  
32  constexpr auto FAN_SENSOR_PATH = "/xyz/openbmc_project/sensors/fan_tach/";
33  constexpr auto FAN_TARGET_PROPERTY = "Target";
34  
35  Fan::Fan(const json& jsonObj) :
36      ConfigBase(jsonObj), _bus(util::SDBusPlus::getBus())
37  {
38      setInterface(jsonObj);
39      setSensors(jsonObj);
40      setZone(jsonObj);
41  }
42  
43  void Fan::setInterface(const json& jsonObj)
44  {
45      if (!jsonObj.contains("target_interface"))
46      {
47          log<level::ERR>("Missing required fan sensor target interface",
48                          entry("JSON=%s", jsonObj.dump().c_str()));
49          throw std::runtime_error(
50              "Missing required fan sensor target interface");
51      }
52      _interface = jsonObj["target_interface"].get<std::string>();
53  }
54  
55  void Fan::setSensors(const json& jsonObj)
56  {
57      if (!jsonObj.contains("sensors"))
58      {
59          log<level::ERR>("Missing required fan sensors list",
60                          entry("JSON=%s", jsonObj.dump().c_str()));
61          throw std::runtime_error("Missing required fan sensors list");
62      }
63      std::string path;
64      for (const auto& sensor : jsonObj["sensors"])
65      {
66          if (!jsonObj.contains("target_path"))
67          {
68              // If target_path is not set in configuration,
69              // it is default to /xyz/openbmc_project/sensors/fan_tach/
70              path = FAN_SENSOR_PATH + sensor.get<std::string>();
71          }
72          else
73          {
74              path = jsonObj["target_path"].get<std::string>() +
75                     sensor.get<std::string>();
76          }
77  
78          auto service = util::SDBusPlus::getService(_bus, path, _interface);
79          _sensors[path] = service;
80      }
81      // All sensors associated with this fan are set to the same target,
82      // so only need to read target property from one of them
83      if (!path.empty())
84      {
85          _target = util::SDBusPlus::getProperty<uint64_t>(
86              _bus, _sensors.at(path), path, _interface, FAN_TARGET_PROPERTY);
87      }
88  }
89  
90  void Fan::setZone(const json& jsonObj)
91  {
92      if (!jsonObj.contains("zone"))
93      {
94          log<level::ERR>("Missing required fan zone",
95                          entry("JSON=%s", jsonObj.dump().c_str()));
96          throw std::runtime_error("Missing required fan zone");
97      }
98      _zone = jsonObj["zone"].get<std::string>();
99  }
100  
101  void Fan::setTarget(uint64_t target)
102  {
103      if ((_target == target) || !_lockedTargets.empty())
104      {
105          return;
106      }
107  
108      for (const auto& sensor : _sensors)
109      {
110          auto value = target;
111          try
112          {
113              util::SDBusPlus::setProperty<uint64_t>(
114                  _bus, sensor.second, sensor.first, _interface,
115                  FAN_TARGET_PROPERTY, std::move(value));
116          }
117          catch (const sdbusplus::exception_t&)
118          {
119              throw util::DBusPropertyError{
120                  std::format("Failed to set target for fan {}", _name).c_str(),
121                  sensor.second, sensor.first, _interface, FAN_TARGET_PROPERTY};
122          }
123      }
124      _target = target;
125  }
126  
127  void Fan::lockTarget(uint64_t target)
128  {
129      // if multiple locks, take highest, else allow only the
130      // first lock to lower the target
131      if (target >= _target || _lockedTargets.empty())
132      {
133          // setTarget wont work if any locked targets exist
134          decltype(_lockedTargets) temp;
135          _lockedTargets.swap(temp);
136  
137          setTarget(target);
138          _lockedTargets.swap(temp);
139      }
140  
141      _lockedTargets.push_back(target);
142  }
143  
144  void Fan::unlockTarget(uint64_t target)
145  {
146      // find and remove the requested lock
147      auto itr(std::find_if(
148          _lockedTargets.begin(), _lockedTargets.end(),
149          [target](auto lockedTarget) { return target == lockedTarget; }));
150  
151      if (_lockedTargets.end() != itr)
152      {
153          _lockedTargets.erase(itr);
154  
155          // if additional locks, re-lock at next-highest target
156          if (!_lockedTargets.empty())
157          {
158              itr = std::max_element(_lockedTargets.begin(),
159                                     _lockedTargets.end());
160  
161              // setTarget wont work if any locked targets exist
162              decltype(_lockedTargets) temp;
163              _lockedTargets.swap(temp);
164              setTarget(*itr);
165              _lockedTargets.swap(temp);
166          }
167      }
168  }
169  
170  } // namespace phosphor::fan::control::json
171