xref: /openbmc/dbus-sensors/src/adc/ADCSensor.cpp (revision d4bc41f295c919c985366a4abe53db99f611c2b5)
1 /*
2 // Copyright (c) 2017 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 "ADCSensor.hpp"
18 
19 #include "SensorPaths.hpp"
20 #include "Thresholds.hpp"
21 #include "Utils.hpp"
22 #include "sensor.hpp"
23 
24 #include <fcntl.h>
25 
26 #include <boost/asio/error.hpp>
27 #include <boost/asio/io_context.hpp>
28 #include <boost/asio/read_until.hpp>
29 #include <boost/asio/streambuf.hpp>
30 #include <phosphor-logging/lg2.hpp>
31 #include <sdbusplus/asio/connection.hpp>
32 #include <sdbusplus/asio/object_server.hpp>
33 
34 #include <chrono>
35 #include <cmath>
36 #include <cstddef>
37 #include <istream>
38 #include <limits>
39 #include <memory>
40 #include <optional>
41 #include <stdexcept>
42 #include <string>
43 #include <utility>
44 #include <vector>
45 
46 // scaling factor from hwmon
47 static constexpr unsigned int sensorScaleFactor = 1000;
48 
49 static constexpr double roundFactor = 10000;     // 3 decimal places
50 static constexpr double maxVoltageReading = 1.8; // pre sensor scaling
51 static constexpr double minVoltageReading = 0;
52 
ADCSensor(const std::string & path,sdbusplus::asio::object_server & objectServer,std::shared_ptr<sdbusplus::asio::connection> & conn,boost::asio::io_context & io,const std::string & sensorName,std::vector<thresholds::Threshold> && thresholdsIn,const double scaleFactor,const float pollRate,PowerState readState,const std::string & sensorConfiguration,std::optional<BridgeGpio> && bridgeGpio)53 ADCSensor::ADCSensor(
54     const std::string& path, sdbusplus::asio::object_server& objectServer,
55     std::shared_ptr<sdbusplus::asio::connection>& conn,
56     boost::asio::io_context& io, const std::string& sensorName,
57     std::vector<thresholds::Threshold>&& thresholdsIn, const double scaleFactor,
58     const float pollRate, PowerState readState,
59     const std::string& sensorConfiguration,
60     std::optional<BridgeGpio>&& bridgeGpio) :
61     Sensor(escapeName(sensorName), std::move(thresholdsIn), sensorConfiguration,
62            "ADC", false, false, maxVoltageReading / scaleFactor,
63            minVoltageReading / scaleFactor, conn, readState),
64     objServer(objectServer), inputDev(io), waitTimer(io), path(path),
65     scaleFactor(scaleFactor),
66     sensorPollMs(static_cast<unsigned int>(pollRate * 1000)),
67     bridgeGpio(std::move(bridgeGpio)), thresholdTimer(io)
68 {
69     // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
70     int fd = open(path.c_str(), O_RDONLY);
71     if (fd < 0)
72     {
73         lg2::error("unable to open acd device");
74     }
75 
76     inputDev.assign(fd);
77 
78     sensorInterface = objectServer.add_interface(
79         "/xyz/openbmc_project/sensors/voltage/" + name,
80         "xyz.openbmc_project.Sensor.Value");
81     for (const auto& threshold : thresholds)
82     {
83         std::string interface = thresholds::getInterface(threshold.level);
84         thresholdInterfaces[static_cast<size_t>(threshold.level)] =
85             objectServer.add_interface(
86                 "/xyz/openbmc_project/sensors/voltage/" + name, interface);
87     }
88     association = objectServer.add_interface(
89         "/xyz/openbmc_project/sensors/voltage/" + name, association::interface);
90     setInitialProperties(sensor_paths::unitVolts);
91 }
92 
~ADCSensor()93 ADCSensor::~ADCSensor()
94 {
95     // close the input dev to cancel async operations
96     inputDev.close();
97     waitTimer.cancel();
98 
99     for (const auto& iface : thresholdInterfaces)
100     {
101         objServer.remove_interface(iface);
102     }
103     objServer.remove_interface(sensorInterface);
104     objServer.remove_interface(association);
105 }
106 
setupRead()107 void ADCSensor::setupRead()
108 {
109     if (!readingStateGood())
110     {
111         markAvailable(false);
112         updateValue(std::numeric_limits<double>::quiet_NaN());
113         restartRead();
114         return;
115     }
116 
117     std::shared_ptr<boost::asio::streambuf> buffer =
118         std::make_shared<boost::asio::streambuf>();
119 
120     std::weak_ptr<ADCSensor> weakRef = weak_from_this();
121 
122     if (bridgeGpio.has_value())
123     {
124         (*bridgeGpio).set(1);
125         // In case a channel has a bridge circuit,we have to turn the bridge on
126         // prior to reading a value at least for one scan cycle to get a valid
127         // value. Guarantee that the HW signal can be stable, the HW signal
128         // could be instability.
129         waitTimer.expires_after(
130             std::chrono::milliseconds(bridgeGpio->setupTimeMs));
131         waitTimer.async_wait(
132             [weakRef, buffer](const boost::system::error_code& ec) {
133                 std::shared_ptr<ADCSensor> self = weakRef.lock();
134                 if (ec == boost::asio::error::operation_aborted)
135                 {
136                     return; // we're being canceled
137                 }
138 
139                 if (self)
140                 {
141                     boost::asio::async_read_until(
142                         self->inputDev, *buffer, '\n',
143                         [weakRef, buffer](const boost::system::error_code& ec,
144                                           std::size_t /*bytes_transfered*/) {
145                             std::shared_ptr<ADCSensor> self = weakRef.lock();
146                             if (self)
147                             {
148                                 self->readBuf = buffer;
149                                 self->handleResponse(ec);
150                             }
151                         });
152                 }
153             });
154     }
155     else
156     {
157         boost::asio::async_read_until(
158             inputDev, *buffer, '\n',
159             [weakRef, buffer](const boost::system::error_code& ec,
160                               std::size_t /*bytes_transfered*/) {
161                 std::shared_ptr<ADCSensor> self = weakRef.lock();
162                 if (self)
163                 {
164                     self->readBuf = buffer;
165                     self->handleResponse(ec);
166                 }
167             });
168     }
169 }
170 
restartRead()171 void ADCSensor::restartRead()
172 {
173     std::weak_ptr<ADCSensor> weakRef = weak_from_this();
174     waitTimer.expires_after(std::chrono::milliseconds(sensorPollMs));
175     waitTimer.async_wait([weakRef](const boost::system::error_code& ec) {
176         std::shared_ptr<ADCSensor> self = weakRef.lock();
177         if (ec == boost::asio::error::operation_aborted)
178         {
179             if (self)
180             {
181                 lg2::error("adcsensor '{NAME}' read cancelled", "NAME",
182                            self->name);
183             }
184             else
185             {
186                 lg2::error("adcsensor read cancelled no self");
187             }
188             return; // we're being canceled
189         }
190 
191         if (self)
192         {
193             self->setupRead();
194         }
195         else
196         {
197             lg2::error("adcsensor weakref no self");
198         }
199     });
200 }
201 
handleResponse(const boost::system::error_code & err)202 void ADCSensor::handleResponse(const boost::system::error_code& err)
203 {
204     std::weak_ptr<ADCSensor> weakRef = weak_from_this();
205 
206     if (err == boost::system::errc::bad_file_descriptor)
207     {
208         return; // we're being destroyed
209     }
210     std::istream responseStream(readBuf.get());
211 
212     if (!err)
213     {
214         std::string response;
215         std::getline(responseStream, response);
216 
217         // todo read scaling factors from configuration
218         try
219         {
220             rawValue = std::stod(response);
221             double nvalue = (rawValue / sensorScaleFactor) / scaleFactor;
222             nvalue = std::round(nvalue * roundFactor) / roundFactor;
223             updateValue(nvalue);
224         }
225         catch (const std::invalid_argument&)
226         {
227             incrementError();
228         }
229     }
230     else
231     {
232         incrementError();
233     }
234 
235     responseStream.clear();
236     inputDev.close();
237     if (bridgeGpio.has_value())
238     {
239         (*bridgeGpio).set(0);
240     }
241 
242     // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
243     int fd = open(path.c_str(), O_RDONLY);
244     if (fd < 0)
245     {
246         lg2::error("adcsensor '{NAME}' failed to open '{PATH}'", "NAME", name,
247                    "PATH", path);
248         return; // we're no longer valid
249     }
250     inputDev.assign(fd);
251     restartRead();
252 }
253 
checkThresholds()254 void ADCSensor::checkThresholds()
255 {
256     if (!readingStateGood())
257     {
258         return;
259     }
260 
261     thresholds::checkThresholdsPowerDelay(weak_from_this(), thresholdTimer);
262 }
263