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