1 /*
2 // Copyright (c) 2018 Intel 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
17 #include "PwmSensor.hpp"
18
19 #include "SensorPaths.hpp"
20 #include "Utils.hpp"
21 #include "sensor.hpp"
22
23 #include <phosphor-logging/lg2.hpp>
24 #include <sdbusplus/asio/connection.hpp>
25 #include <sdbusplus/asio/object_server.hpp>
26
27 #include <cmath>
28 #include <cstdint>
29 #include <fstream>
30 #include <memory>
31 #include <stdexcept>
32 #include <string>
33
34 static constexpr double sysPwmMax = 255.0;
35 static constexpr double psuPwmMax = 100.0;
36 static constexpr double defaultPwm = 30.0;
37 static constexpr double targetIfaceMax = sysPwmMax;
38
PwmSensor(const std::string & pwmname,const std::string & sysPath,std::shared_ptr<sdbusplus::asio::connection> & conn,sdbusplus::asio::object_server & objectServer,const std::string & sensorConfiguration,const std::string & sensorType,bool isValueMutable)39 PwmSensor::PwmSensor(const std::string& pwmname, const std::string& sysPath,
40 std::shared_ptr<sdbusplus::asio::connection>& conn,
41 sdbusplus::asio::object_server& objectServer,
42 const std::string& sensorConfiguration,
43 const std::string& sensorType, bool isValueMutable) :
44 sysPath(sysPath), objectServer(objectServer),
45 name(sensor_paths::escapePathForDbus(pwmname))
46 {
47 // add interface under sensor and Control.FanPwm as Control is used
48 // in obmc project, also add sensor so it can be viewed as a sensor
49 sensorInterface = objectServer.add_interface(
50 "/xyz/openbmc_project/sensors/fan_pwm/" + name,
51 "xyz.openbmc_project.Sensor.Value");
52 uint32_t pwmValue = getValue(false);
53 if (sensorType == "PSU")
54 {
55 pwmMax = psuPwmMax;
56 }
57 else
58 {
59 pwmMax = sysPwmMax;
60 }
61
62 if (pwmValue == 0U)
63 {
64 // default pwm to non 0
65 pwmValue = static_cast<uint32_t>(pwmMax * (defaultPwm / 100.0));
66 setValue(pwmValue);
67 }
68 double fValue = 100.0 * (static_cast<double>(pwmValue) / pwmMax);
69 sensorInterface->register_property(
70 "Value", fValue,
71 [this](const double& req, double& resp) {
72 if (!std::isfinite(req))
73 {
74 // Reject attempted change, if to NaN or other non-numeric
75 return -1;
76 }
77 if (req > 100.0 || req < 0.0)
78 {
79 // TODO(): It does not seem desirable to halt daemon here,
80 // probably should just reject the change, continue running?
81 throw std::runtime_error("Value out of range");
82 return -1;
83 }
84
85 double reqValue = (req / 100.0) * pwmMax;
86 double respValue = (resp / 100.0) * pwmMax;
87 auto reqInt = static_cast<uint32_t>(std::round(reqValue));
88 auto respInt = static_cast<uint32_t>(std::round(respValue));
89 // Avoid floating-point equality, compare as integers
90 if (reqInt == respInt)
91 {
92 return 1;
93 }
94 setValue(reqInt);
95 resp = req;
96
97 controlInterface->signal_property("Target");
98
99 return 1;
100 },
101 [this](double& curVal) {
102 double currScaled = (curVal / 100.0) * pwmMax;
103 auto currInt = static_cast<uint32_t>(std::round(currScaled));
104 auto getInt = getValue();
105 // Avoid floating-point equality, compare as integers
106 if (currInt != getInt)
107 {
108 double getScaled =
109 100.0 * (static_cast<double>(getInt) / pwmMax);
110 curVal = getScaled;
111 controlInterface->signal_property("Target");
112 sensorInterface->signal_property("Value");
113 }
114 return curVal;
115 });
116 // pwm sensor interface is in percent
117 sensorInterface->register_property("MaxValue", static_cast<double>(100));
118 sensorInterface->register_property("MinValue", static_cast<double>(0));
119 sensorInterface->register_property("Unit", sensor_paths::unitPercent);
120
121 controlInterface = objectServer.add_interface(
122 "/xyz/openbmc_project/control/fanpwm/" + name,
123 "xyz.openbmc_project.Control.FanPwm");
124 controlInterface->register_property(
125 "Target", static_cast<uint64_t>(pwmValue),
126 [this](const uint64_t& req, uint64_t& resp) {
127 if (req > static_cast<uint64_t>(targetIfaceMax))
128 {
129 throw std::runtime_error("Value out of range");
130 return -1;
131 }
132 if (req == resp)
133 {
134 return 1;
135 }
136 auto scaledValue = static_cast<double>(req) / targetIfaceMax;
137 auto roundValue = std::round(scaledValue * pwmMax);
138 setValue(static_cast<uint32_t>(roundValue));
139 resp = req;
140
141 sensorInterface->signal_property("Value");
142
143 return 1;
144 },
145 [this](uint64_t& curVal) {
146 auto getInt = getValue();
147 auto scaledValue = static_cast<double>(getInt) / pwmMax;
148 auto roundValue = std::round(scaledValue * targetIfaceMax);
149 auto value = static_cast<uint64_t>(roundValue);
150 if (curVal != value)
151 {
152 curVal = value;
153 controlInterface->signal_property("Target");
154 sensorInterface->signal_property("Value");
155 }
156 return curVal;
157 });
158
159 sensorInterface->initialize();
160 controlInterface->initialize();
161
162 if (isValueMutable)
163 {
164 valueMutabilityInterface =
165 std::make_shared<sdbusplus::asio::dbus_interface>(
166 conn, sensorInterface->get_object_path(),
167 valueMutabilityInterfaceName);
168 valueMutabilityInterface->register_property("Mutable", true);
169 if (!valueMutabilityInterface->initialize())
170 {
171 lg2::error("error initializing sensor value mutability interface");
172 valueMutabilityInterface = nullptr;
173 }
174 }
175
176 association = objectServer.add_interface(
177 "/xyz/openbmc_project/sensors/fan_pwm/" + name, association::interface);
178
179 // PowerSupply sensors should be associated with chassis board path
180 // and inventory along with psu object.
181 if (sensorType == "PSU")
182 {
183 createInventoryAssoc(conn, association, sensorConfiguration);
184 }
185 else
186 {
187 createAssociation(association, sensorConfiguration);
188 }
189 }
~PwmSensor()190 PwmSensor::~PwmSensor()
191 {
192 objectServer.remove_interface(sensorInterface);
193 objectServer.remove_interface(controlInterface);
194 objectServer.remove_interface(association);
195 }
196
setValue(uint32_t value)197 void PwmSensor::setValue(uint32_t value)
198 {
199 std::ofstream ref(sysPath);
200 if (!ref.good())
201 {
202 throw std::runtime_error("Bad Write File");
203 }
204 ref << value;
205 }
206
207 // on success returns pwm, on failure throws except on initialization, where it
208 // prints an error and returns 0
getValue(bool errThrow)209 uint32_t PwmSensor::getValue(bool errThrow)
210 {
211 std::ifstream ref(sysPath);
212 if (!ref.good())
213 {
214 lg2::error("Error opening '{PATH}'", "PATH", sysPath);
215 return 0;
216 }
217 std::string line;
218 if (!std::getline(ref, line))
219 {
220 lg2::error("Error reading pwm at '{PATH}'", "PATH", sysPath);
221 return 0;
222 }
223 try
224 {
225 uint32_t value = std::stoi(line);
226 return value;
227 }
228 catch (const std::invalid_argument&)
229 {
230 lg2::error("Error converting pwm");
231 // throw if not initial read to be caught by dbus bindings
232 if (errThrow)
233 {
234 throw std::runtime_error("Bad Read");
235 }
236 }
237 return 0;
238 }
239