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