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 <memory>
38 #include <optional>
39 #include <stdexcept>
40 #include <string>
41 #include <utility>
42 #include <vector>
43
44 // scaling factor from hwmon
45 static constexpr unsigned int sensorScaleFactor = 1000;
46
47 static constexpr double roundFactor = 10000; // 3 decimal places
48 static constexpr double maxVoltageReading = 1.8; // pre sensor scaling
49 static constexpr double minVoltageReading = 0;
50
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)51 ADCSensor::ADCSensor(const std::string& path,
52 sdbusplus::asio::object_server& objectServer,
53 std::shared_ptr<sdbusplus::asio::connection>& conn,
54 boost::asio::io_context& io, const std::string& sensorName,
55 std::vector<thresholds::Threshold>&& thresholdsIn,
56 const double scaleFactor, const float pollRate,
57 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 std::shared_ptr<boost::asio::streambuf> buffer =
109 std::make_shared<boost::asio::streambuf>();
110
111 std::weak_ptr<ADCSensor> weakRef = weak_from_this();
112
113 if (bridgeGpio.has_value())
114 {
115 (*bridgeGpio).set(1);
116 // In case a channel has a bridge circuit,we have to turn the bridge on
117 // prior to reading a value at least for one scan cycle to get a valid
118 // value. Guarantee that the HW signal can be stable, the HW signal
119 // could be instability.
120 waitTimer.expires_after(
121 std::chrono::milliseconds(bridgeGpio->setupTimeMs));
122 waitTimer.async_wait(
123 [weakRef, buffer](const boost::system::error_code& ec) {
124 std::shared_ptr<ADCSensor> self = weakRef.lock();
125 if (ec == boost::asio::error::operation_aborted)
126 {
127 return; // we're being canceled
128 }
129
130 if (self)
131 {
132 boost::asio::async_read_until(
133 self->inputDev, *buffer, '\n',
134 [weakRef, buffer](const boost::system::error_code& ec,
135 std::size_t /*bytes_transfered*/) {
136 std::shared_ptr<ADCSensor> self = weakRef.lock();
137 if (self)
138 {
139 self->readBuf = buffer;
140 self->handleResponse(ec);
141 }
142 });
143 }
144 });
145 }
146 else
147 {
148 boost::asio::async_read_until(
149 inputDev, *buffer, '\n',
150 [weakRef, buffer](const boost::system::error_code& ec,
151 std::size_t /*bytes_transfered*/) {
152 std::shared_ptr<ADCSensor> self = weakRef.lock();
153 if (self)
154 {
155 self->readBuf = buffer;
156 self->handleResponse(ec);
157 }
158 });
159 }
160 }
161
handleResponse(const boost::system::error_code & err)162 void ADCSensor::handleResponse(const boost::system::error_code& err)
163 {
164 std::weak_ptr<ADCSensor> weakRef = weak_from_this();
165
166 if (err == boost::system::errc::bad_file_descriptor)
167 {
168 return; // we're being destroyed
169 }
170 std::istream responseStream(readBuf.get());
171
172 if (!err)
173 {
174 std::string response;
175 std::getline(responseStream, response);
176
177 // todo read scaling factors from configuration
178 try
179 {
180 rawValue = std::stod(response);
181 double nvalue = (rawValue / sensorScaleFactor) / scaleFactor;
182 nvalue = std::round(nvalue * roundFactor) / roundFactor;
183 updateValue(nvalue);
184 }
185 catch (const std::invalid_argument&)
186 {
187 incrementError();
188 }
189 }
190 else
191 {
192 incrementError();
193 }
194
195 responseStream.clear();
196 inputDev.close();
197 if (bridgeGpio.has_value())
198 {
199 (*bridgeGpio).set(0);
200 }
201
202 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
203 int fd = open(path.c_str(), O_RDONLY);
204 if (fd < 0)
205 {
206 std::cerr << "adcsensor " << name << " failed to open " << path << "\n";
207 return; // we're no longer valid
208 }
209 inputDev.assign(fd);
210 waitTimer.expires_after(std::chrono::milliseconds(sensorPollMs));
211 waitTimer.async_wait([weakRef](const boost::system::error_code& ec) {
212 std::shared_ptr<ADCSensor> self = weakRef.lock();
213 if (ec == boost::asio::error::operation_aborted)
214 {
215 if (self)
216 {
217 std::cerr << "adcsensor " << self->name << " read cancelled\n";
218 }
219 else
220 {
221 std::cerr << "adcsensor read cancelled no self\n";
222 }
223 return; // we're being canceled
224 }
225
226 if (self)
227 {
228 self->setupRead();
229 }
230 else
231 {
232 std::cerr << "adcsensor weakref no self\n";
233 }
234 });
235 }
236
checkThresholds()237 void ADCSensor::checkThresholds()
238 {
239 if (!readingStateGood())
240 {
241 return;
242 }
243
244 thresholds::checkThresholdsPowerDelay(weak_from_this(), thresholdTimer);
245 }
246