xref: /openbmc/dbus-sensors/src/PwmSensor.cpp (revision 383c5e3358f7561b918629d893f8c6f395295414)
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             // Scale Value to match Target unit
95             auto scaledValue = (req / 100.0) * targetIfaceMax;
96             auto targetValue = static_cast<uint64_t>(std::round(scaledValue));
97             controlInterface->set_property("Target", targetValue);
98             resp = req;
99 
100             return 1;
101         },
102         [this](double& curVal) {
103             double currScaled = (curVal / 100.0) * pwmMax;
104             auto currInt = static_cast<uint32_t>(std::round(currScaled));
105             auto getInt = getValue();
106             // Avoid floating-point equality, compare as integers
107             if (currInt != getInt)
108             {
109                 double getScaled =
110                     100.0 * (static_cast<double>(getInt) / pwmMax);
111                 curVal = getScaled;
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                 sensorInterface->signal_property("Value");
153             }
154             return curVal;
155         });
156 
157     sensorInterface->initialize();
158     controlInterface->initialize();
159 
160     if (isValueMutable)
161     {
162         valueMutabilityInterface =
163             std::make_shared<sdbusplus::asio::dbus_interface>(
164                 conn, sensorInterface->get_object_path(),
165                 valueMutabilityInterfaceName);
166         valueMutabilityInterface->register_property("Mutable", true);
167         if (!valueMutabilityInterface->initialize())
168         {
169             lg2::error("error initializing sensor value mutability interface");
170             valueMutabilityInterface = nullptr;
171         }
172     }
173 
174     association = objectServer.add_interface(
175         "/xyz/openbmc_project/sensors/fan_pwm/" + name, association::interface);
176 
177     // PowerSupply sensors should be associated with chassis board path
178     // and inventory along with psu object.
179     if (sensorType == "PSU")
180     {
181         createInventoryAssoc(conn, association, sensorConfiguration);
182     }
183     else
184     {
185         createAssociation(association, sensorConfiguration);
186     }
187 }
~PwmSensor()188 PwmSensor::~PwmSensor()
189 {
190     objectServer.remove_interface(sensorInterface);
191     objectServer.remove_interface(controlInterface);
192     objectServer.remove_interface(association);
193 }
194 
setValue(uint32_t value)195 void PwmSensor::setValue(uint32_t value)
196 {
197     std::ofstream ref(sysPath);
198     if (!ref.good())
199     {
200         throw std::runtime_error("Bad Write File");
201     }
202     ref << value;
203 }
204 
205 // on success returns pwm, on failure throws except on initialization, where it
206 // prints an error and returns 0
getValue(bool errThrow)207 uint32_t PwmSensor::getValue(bool errThrow)
208 {
209     std::ifstream ref(sysPath);
210     if (!ref.good())
211     {
212         lg2::error("Error opening '{PATH}'", "PATH", sysPath);
213         return 0;
214     }
215     std::string line;
216     if (!std::getline(ref, line))
217     {
218         lg2::error("Error reading pwm at '{PATH}'", "PATH", sysPath);
219         return 0;
220     }
221     try
222     {
223         uint32_t value = std::stoi(line);
224         return value;
225     }
226     catch (const std::invalid_argument&)
227     {
228         lg2::error("Error converting pwm");
229         // throw if not initial read to be caught by dbus bindings
230         if (errThrow)
231         {
232             throw std::runtime_error("Bad Read");
233         }
234     }
235     return 0;
236 }
237