xref: /openbmc/dbus-sensors/src/PwmSensor.cpp (revision eacbfdd1)
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 <sdbusplus/asio/connection.hpp>
24 #include <sdbusplus/asio/object_server.hpp>
25 
26 #include <cmath>
27 #include <cstdint>
28 #include <fstream>
29 #include <iostream>
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),
45     objectServer(objectServer), 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 = 100.0 * (static_cast<double>(getInt) / pwmMax);
109             curVal = getScaled;
110             controlInterface->signal_property("Target");
111             sensorInterface->signal_property("Value");
112         }
113         return curVal;
114     });
115     // pwm sensor interface is in percent
116     sensorInterface->register_property("MaxValue", static_cast<double>(100));
117     sensorInterface->register_property("MinValue", static_cast<double>(0));
118     sensorInterface->register_property("Unit", sensor_paths::unitPercent);
119 
120     controlInterface = objectServer.add_interface(
121         "/xyz/openbmc_project/control/fanpwm/" + name,
122         "xyz.openbmc_project.Control.FanPwm");
123     controlInterface->register_property(
124         "Target", static_cast<uint64_t>(pwmValue),
125         [this](const uint64_t& req, uint64_t& resp) {
126         if (req > static_cast<uint64_t>(targetIfaceMax))
127         {
128             throw std::runtime_error("Value out of range");
129             return -1;
130         }
131         if (req == resp)
132         {
133             return 1;
134         }
135         auto scaledValue = static_cast<double>(req) / targetIfaceMax;
136         auto roundValue = std::round(scaledValue * pwmMax);
137         setValue(static_cast<uint32_t>(roundValue));
138         resp = req;
139 
140         sensorInterface->signal_property("Value");
141 
142         return 1;
143     },
144         [this](uint64_t& curVal) {
145         auto getInt = getValue();
146         auto scaledValue = static_cast<double>(getInt) / pwmMax;
147         auto roundValue = std::round(scaledValue * targetIfaceMax);
148         auto value = static_cast<uint64_t>(roundValue);
149         if (curVal != value)
150         {
151             curVal = value;
152             controlInterface->signal_property("Target");
153             sensorInterface->signal_property("Value");
154         }
155         return curVal;
156     });
157 
158     sensorInterface->initialize();
159     controlInterface->initialize();
160 
161     if (isValueMutable)
162     {
163         valueMutabilityInterface =
164             std::make_shared<sdbusplus::asio::dbus_interface>(
165                 conn, sensorInterface->get_object_path(),
166                 valueMutabilityInterfaceName);
167         valueMutabilityInterface->register_property("Mutable", true);
168         if (!valueMutabilityInterface->initialize())
169         {
170             std::cerr
171                 << "error initializing sensor value mutability interface\n";
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         return -1;
215     }
216     std::string line;
217     if (!std::getline(ref, line))
218     {
219         return -1;
220     }
221     try
222     {
223         uint32_t value = std::stoi(line);
224         return value;
225     }
226     catch (const std::invalid_argument&)
227     {
228         std::cerr << "Error reading pwm at " << sysPath << "\n";
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