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 "TachSensor.hpp"
18
19 #include "SensorPaths.hpp"
20 #include "Thresholds.hpp"
21 #include "Utils.hpp"
22 #include "sensor.hpp"
23
24 #include <boost/asio/buffer.hpp>
25 #include <boost/asio/error.hpp>
26 #include <boost/asio/io_context.hpp>
27 #include <boost/asio/posix/stream_descriptor.hpp>
28 #include <boost/asio/random_access_file.hpp>
29 #include <gpiod.hpp>
30 #include <sdbusplus/asio/connection.hpp>
31 #include <sdbusplus/asio/object_server.hpp>
32
33 #include <charconv>
34 #include <chrono>
35 #include <cstddef>
36 #include <cstdint>
37 #include <iostream>
38 #include <memory>
39 #include <optional>
40 #include <string>
41 #include <system_error>
42 #include <utility>
43 #include <vector>
44
45 static constexpr unsigned int pwmPollMs = 500;
46
TachSensor(const std::string & path,const std::string & objectType,sdbusplus::asio::object_server & objectServer,std::shared_ptr<sdbusplus::asio::connection> & conn,std::unique_ptr<PresenceSensor> && presenceSensor,std::optional<RedundancySensor> * redundancy,boost::asio::io_context & io,const std::string & fanName,std::vector<thresholds::Threshold> && thresholdsIn,const std::string & sensorConfiguration,const std::pair<double,double> & limits,const PowerState & powerState,const std::optional<std::string> & ledIn)47 TachSensor::TachSensor(const std::string& path, const std::string& objectType,
48 sdbusplus::asio::object_server& objectServer,
49 std::shared_ptr<sdbusplus::asio::connection>& conn,
50 std::unique_ptr<PresenceSensor>&& presenceSensor,
51 std::optional<RedundancySensor>* redundancy,
52 boost::asio::io_context& io, const std::string& fanName,
53 std::vector<thresholds::Threshold>&& thresholdsIn,
54 const std::string& sensorConfiguration,
55 const std::pair<double, double>& limits,
56 const PowerState& powerState,
57 const std::optional<std::string>& ledIn) :
58 Sensor(escapeName(fanName), std::move(thresholdsIn), sensorConfiguration,
59 objectType, false, false, limits.second, limits.first, conn,
60 powerState),
61 objServer(objectServer), redundancy(redundancy),
62 presence(std::move(presenceSensor)),
63 inputDev(io, path, boost::asio::random_access_file::read_only),
64 waitTimer(io), path(path), led(ledIn)
65 {
66 sensorInterface = objectServer.add_interface(
67 "/xyz/openbmc_project/sensors/fan_tach/" + name,
68 "xyz.openbmc_project.Sensor.Value");
69
70 for (const auto& threshold : thresholds)
71 {
72 std::string interface = thresholds::getInterface(threshold.level);
73 thresholdInterfaces[static_cast<size_t>(threshold.level)] =
74 objectServer.add_interface(
75 "/xyz/openbmc_project/sensors/fan_tach/" + name, interface);
76 }
77 association = objectServer.add_interface(
78 "/xyz/openbmc_project/sensors/fan_tach/" + name,
79 association::interface);
80
81 if (presence)
82 {
83 itemIface =
84 objectServer.add_interface("/xyz/openbmc_project/inventory/" + name,
85 "xyz.openbmc_project.Inventory.Item");
86 itemIface->register_property("PrettyName",
87 std::string()); // unused property
88 itemIface->register_property("Present", true);
89 itemIface->initialize();
90 itemAssoc = objectServer.add_interface(
91 "/xyz/openbmc_project/inventory/" + name, association::interface);
92 itemAssoc->register_property(
93 "Associations",
94 std::vector<Association>{
95 {"sensors", "inventory",
96 "/xyz/openbmc_project/sensors/fan_tach/" + name}});
97 itemAssoc->initialize();
98 }
99 setInitialProperties(sensor_paths::unitRPMs);
100 }
101
~TachSensor()102 TachSensor::~TachSensor()
103 {
104 // close the input dev to cancel async operations
105 inputDev.close();
106 waitTimer.cancel();
107 for (const auto& iface : thresholdInterfaces)
108 {
109 objServer.remove_interface(iface);
110 }
111 objServer.remove_interface(sensorInterface);
112 objServer.remove_interface(association);
113 objServer.remove_interface(itemIface);
114 objServer.remove_interface(itemAssoc);
115 }
116
setupRead()117 void TachSensor::setupRead()
118 {
119 std::weak_ptr<TachSensor> weakRef = weak_from_this();
120 inputDev.async_read_some_at(
121 0, boost::asio::buffer(readBuf),
122 [weakRef](const boost::system::error_code& ec, std::size_t bytesRead) {
123 std::shared_ptr<TachSensor> self = weakRef.lock();
124 if (self)
125 {
126 self->handleResponse(ec, bytesRead);
127 }
128 });
129 }
130
restartRead(size_t pollTime)131 void TachSensor::restartRead(size_t pollTime)
132 {
133 std::weak_ptr<TachSensor> weakRef = weak_from_this();
134 waitTimer.expires_after(std::chrono::milliseconds(pollTime));
135 waitTimer.async_wait([weakRef](const boost::system::error_code& ec) {
136 if (ec == boost::asio::error::operation_aborted)
137 {
138 return; // we're being canceled
139 }
140 std::shared_ptr<TachSensor> self = weakRef.lock();
141 if (!self)
142 {
143 return;
144 }
145 self->setupRead();
146 });
147 }
148
handleResponse(const boost::system::error_code & err,size_t bytesRead)149 void TachSensor::handleResponse(const boost::system::error_code& err,
150 size_t bytesRead)
151 {
152 if ((err == boost::system::errc::bad_file_descriptor) ||
153 (err == boost::asio::error::misc_errors::not_found))
154 {
155 std::cerr << "TachSensor " << name << " removed " << path << "\n";
156 return; // we're being destroyed
157 }
158 bool missing = false;
159 size_t pollTime = pwmPollMs;
160 if (presence)
161 {
162 if (!presence->getValue())
163 {
164 markAvailable(false);
165 missing = true;
166 pollTime = sensorFailedPollTimeMs;
167 }
168 itemIface->set_property("Present", !missing);
169 }
170
171 if (!missing)
172 {
173 if (!err)
174 {
175 const char* bufEnd = readBuf.data() + bytesRead;
176 int nvalue = 0;
177 std::from_chars_result ret = std::from_chars(readBuf.data(), bufEnd,
178 nvalue);
179 if (ret.ec != std::errc())
180 {
181 incrementError();
182 pollTime = sensorFailedPollTimeMs;
183 }
184 else
185 {
186 updateValue(nvalue);
187 }
188 }
189 else
190 {
191 incrementError();
192 pollTime = sensorFailedPollTimeMs;
193 }
194 }
195
196 restartRead(pollTime);
197 }
198
checkThresholds()199 void TachSensor::checkThresholds()
200 {
201 bool status = thresholds::checkThresholds(this);
202
203 if ((redundancy != nullptr) && *redundancy)
204 {
205 (*redundancy)
206 ->update("/xyz/openbmc_project/sensors/fan_tach/" + name, !status);
207 }
208
209 bool curLed = !status;
210 if (led && ledState != curLed)
211 {
212 ledState = curLed;
213 setLed(dbusConnection, *led, curLed);
214 }
215 }
216
PresenceSensor(const std::string & gpioName,bool inverted,boost::asio::io_context & io,const std::string & name)217 PresenceSensor::PresenceSensor(const std::string& gpioName, bool inverted,
218 boost::asio::io_context& io,
219 const std::string& name) :
220 gpioLine(gpiod::find_line(gpioName)),
221 gpioFd(io), name(name)
222 {
223 if (!gpioLine)
224 {
225 std::cerr << "Error requesting gpio: " << gpioName << "\n";
226 status = false;
227 return;
228 }
229
230 try
231 {
232 gpioLine.request({"FanSensor", gpiod::line_request::EVENT_BOTH_EDGES,
233 inverted ? gpiod::line_request::FLAG_ACTIVE_LOW : 0});
234 status = (gpioLine.get_value() != 0);
235
236 int gpioLineFd = gpioLine.event_get_fd();
237 if (gpioLineFd < 0)
238 {
239 std::cerr << "Failed to get " << gpioName << " fd\n";
240 return;
241 }
242
243 gpioFd.assign(gpioLineFd);
244 }
245 catch (const std::system_error&)
246 {
247 std::cerr << "Error reading gpio: " << gpioName << "\n";
248 status = false;
249 return;
250 }
251
252 monitorPresence();
253 }
254
~PresenceSensor()255 PresenceSensor::~PresenceSensor()
256 {
257 gpioFd.close();
258 gpioLine.release();
259 }
260
monitorPresence()261 void PresenceSensor::monitorPresence()
262 {
263 gpioFd.async_wait(boost::asio::posix::stream_descriptor::wait_read,
264 [this](const boost::system::error_code& ec) {
265 if (ec == boost::system::errc::bad_file_descriptor)
266 {
267 return; // we're being destroyed
268 }
269 if (ec)
270 {
271 std::cerr << "Error on presence sensor " << name << " \n";
272 ;
273 }
274 else
275 {
276 read();
277 }
278 monitorPresence();
279 });
280 }
281
read()282 void PresenceSensor::read()
283 {
284 gpioLine.event_read();
285 status = (gpioLine.get_value() != 0);
286 // Read is invoked when an edge event is detected by monitorPresence
287 if (status)
288 {
289 logFanInserted(name);
290 }
291 else
292 {
293 logFanRemoved(name);
294 }
295 }
296
getValue() const297 bool PresenceSensor::getValue() const
298 {
299 return status;
300 }
301
RedundancySensor(size_t count,const std::vector<std::string> & children,sdbusplus::asio::object_server & objectServer,const std::string & sensorConfiguration)302 RedundancySensor::RedundancySensor(size_t count,
303 const std::vector<std::string>& children,
304 sdbusplus::asio::object_server& objectServer,
305 const std::string& sensorConfiguration) :
306 count(count),
307 iface(objectServer.add_interface(
308 "/xyz/openbmc_project/control/FanRedundancy/Tach",
309 "xyz.openbmc_project.Control.FanRedundancy")),
310 association(objectServer.add_interface(
311 "/xyz/openbmc_project/control/FanRedundancy/Tach",
312 association::interface)),
313 objectServer(objectServer)
314 {
315 createAssociation(association, sensorConfiguration);
316 iface->register_property("Collection", children);
317 iface->register_property("Status", std::string("Full"));
318 iface->register_property("AllowedFailures", static_cast<uint8_t>(count));
319 iface->initialize();
320 }
~RedundancySensor()321 RedundancySensor::~RedundancySensor()
322 {
323 objectServer.remove_interface(association);
324 objectServer.remove_interface(iface);
325 }
update(const std::string & name,bool failed)326 void RedundancySensor::update(const std::string& name, bool failed)
327 {
328 statuses[name] = failed;
329 size_t failedCount = 0;
330
331 std::string newState = redundancy::full;
332 for (const auto& [name, status] : statuses)
333 {
334 if (status)
335 {
336 failedCount++;
337 }
338 if (failedCount > count)
339 {
340 newState = redundancy::failed;
341 break;
342 }
343 if (failedCount != 0U)
344 {
345 newState = redundancy::degraded;
346 }
347 }
348 if (state != newState)
349 {
350 if (state == redundancy::full)
351 {
352 logFanRedundancyLost();
353 }
354 else if (newState == redundancy::full)
355 {
356 logFanRedundancyRestored();
357 }
358 state = newState;
359 iface->set_property("Status", state);
360 }
361 }
362