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