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 #include "Thresholds.hpp"
19 #include "Utils.hpp"
20 #include "VariantVisitors.hpp"
21
22 #include <boost/algorithm/string/case_conv.hpp>
23 #include <boost/asio/error.hpp>
24 #include <boost/asio/io_context.hpp>
25 #include <boost/asio/post.hpp>
26 #include <boost/asio/steady_timer.hpp>
27 #include <boost/container/flat_map.hpp>
28 #include <boost/container/flat_set.hpp>
29 #include <gpiod.hpp>
30 #include <phosphor-logging/lg2.hpp>
31 #include <sdbusplus/asio/connection.hpp>
32 #include <sdbusplus/asio/object_server.hpp>
33 #include <sdbusplus/bus.hpp>
34 #include <sdbusplus/bus/match.hpp>
35 #include <sdbusplus/message.hpp>
36 #include <sdbusplus/message/native_types.hpp>
37
38 #include <array>
39 #include <chrono>
40 #include <cstddef>
41 #include <filesystem>
42 #include <fstream>
43 #include <functional>
44 #include <memory>
45 #include <optional>
46 #include <regex>
47 #include <stdexcept>
48 #include <string>
49 #include <utility>
50 #include <variant>
51 #include <vector>
52
53 static constexpr float pollRateDefault = 0.5;
54 static constexpr float gpioBridgeSetupTimeDefault = 0.02;
55
56 static constexpr auto sensorTypes{std::to_array<const char*>({"ADC"})};
57 static std::regex inputRegex(R"(in(\d+)_input)");
58
59 static boost::container::flat_map<size_t, bool> cpuPresence;
60
61 enum class UpdateType
62 {
63 init,
64 cpuPresenceChange
65 };
66
67 // filter out adc from any other voltage sensor
isAdc(const std::filesystem::path & parentPath)68 bool isAdc(const std::filesystem::path& parentPath)
69 {
70 std::filesystem::path namePath = parentPath / "name";
71
72 std::ifstream nameFile(namePath);
73 if (!nameFile.good())
74 {
75 lg2::error("Failure reading '{PATH}'", "PATH", namePath.string());
76 return false;
77 }
78
79 std::string name;
80 std::getline(nameFile, name);
81
82 return name == "iio_hwmon";
83 }
84
createSensors(boost::asio::io_context & io,sdbusplus::asio::object_server & objectServer,boost::container::flat_map<std::string,std::shared_ptr<ADCSensor>> & sensors,std::shared_ptr<sdbusplus::asio::connection> & dbusConnection,const std::shared_ptr<boost::container::flat_set<std::string>> & sensorsChanged,UpdateType updateType)85 void createSensors(
86 boost::asio::io_context& io, sdbusplus::asio::object_server& objectServer,
87 boost::container::flat_map<std::string, std::shared_ptr<ADCSensor>>&
88 sensors,
89 std::shared_ptr<sdbusplus::asio::connection>& dbusConnection,
90 const std::shared_ptr<boost::container::flat_set<std::string>>&
91 sensorsChanged,
92 UpdateType updateType)
93 {
94 auto getter = std::make_shared<GetSensorConfiguration>(
95 dbusConnection,
96 [&io, &objectServer, &sensors, &dbusConnection, sensorsChanged,
97 updateType](const ManagedObjectType& sensorConfigurations) {
98 bool firstScan = sensorsChanged == nullptr;
99 std::vector<std::filesystem::path> paths;
100 if (!findFiles(std::filesystem::path("/sys/class/hwmon"),
101 R"(in\d+_input)", paths))
102 {
103 lg2::error("No adc sensors in system");
104 return;
105 }
106
107 // iterate through all found adc sensors, and try to match them with
108 // configuration
109 for (auto& path : paths)
110 {
111 if (!isAdc(path.parent_path()))
112 {
113 continue;
114 }
115 std::smatch match;
116 std::string pathStr = path.string();
117
118 std::regex_search(pathStr, match, inputRegex);
119 std::string indexStr = *(match.begin() + 1);
120
121 // convert to 0 based
122 size_t index = std::stoul(indexStr) - 1;
123
124 const SensorData* sensorData = nullptr;
125 const std::string* interfacePath = nullptr;
126 const std::pair<std::string, SensorBaseConfigMap>*
127 baseConfiguration = nullptr;
128 for (const auto& [path, cfgData] : sensorConfigurations)
129 {
130 // clear it out each loop
131 baseConfiguration = nullptr;
132
133 // find base configuration
134 for (const char* type : sensorTypes)
135 {
136 auto sensorBase =
137 cfgData.find(configInterfaceName(type));
138 if (sensorBase != cfgData.end())
139 {
140 baseConfiguration = &(*sensorBase);
141 break;
142 }
143 }
144 if (baseConfiguration == nullptr)
145 {
146 continue;
147 }
148 auto findIndex = baseConfiguration->second.find("Index");
149 if (findIndex == baseConfiguration->second.end())
150 {
151 lg2::error(
152 "Base configuration missing Index: '{INTERFACE}'",
153 "INTERFACE", baseConfiguration->first);
154 continue;
155 }
156
157 unsigned int number = std::visit(
158 VariantToUnsignedIntVisitor(), findIndex->second);
159
160 if (number != index)
161 {
162 continue;
163 }
164
165 sensorData = &cfgData;
166 interfacePath = &path.str;
167 break;
168 }
169 if (sensorData == nullptr)
170 {
171 lg2::debug("failed to find match for '{PATH}'", "PATH",
172 path.string());
173 continue;
174 }
175
176 if (baseConfiguration == nullptr)
177 {
178 lg2::error("error finding base configuration for '{PATH}'",
179 "PATH", path.string());
180 continue;
181 }
182
183 auto findSensorName = baseConfiguration->second.find("Name");
184 if (findSensorName == baseConfiguration->second.end())
185 {
186 lg2::error(
187 "could not determine configuration name for '{PATH}'",
188 "PATH", path.string());
189 continue;
190 }
191 std::string sensorName =
192 std::get<std::string>(findSensorName->second);
193
194 // on rescans, only update sensors we were signaled by
195 auto findSensor = sensors.find(sensorName);
196 if (!firstScan && findSensor != sensors.end())
197 {
198 bool found = false;
199 for (auto it = sensorsChanged->begin();
200 it != sensorsChanged->end(); it++)
201 {
202 if (findSensor->second &&
203 it->ends_with(findSensor->second->name))
204 {
205 sensorsChanged->erase(it);
206 findSensor->second = nullptr;
207 found = true;
208 break;
209 }
210 }
211 if (!found)
212 {
213 continue;
214 }
215 }
216
217 auto findCPU = baseConfiguration->second.find("CPURequired");
218 if (findCPU != baseConfiguration->second.end())
219 {
220 size_t index =
221 std::visit(VariantToIntVisitor(), findCPU->second);
222 auto presenceFind = cpuPresence.find(index);
223 if (presenceFind == cpuPresence.end())
224 {
225 continue; // no such cpu
226 }
227 if (!presenceFind->second)
228 {
229 continue; // cpu not installed
230 }
231 }
232 else if (updateType == UpdateType::cpuPresenceChange)
233 {
234 continue;
235 }
236
237 std::vector<thresholds::Threshold> sensorThresholds;
238 if (!parseThresholdsFromConfig(*sensorData, sensorThresholds))
239 {
240 lg2::error("error populating thresholds for '{NAME}'",
241 "NAME", sensorName);
242 }
243
244 auto findScaleFactor =
245 baseConfiguration->second.find("ScaleFactor");
246 float scaleFactor = 1.0;
247 if (findScaleFactor != baseConfiguration->second.end())
248 {
249 scaleFactor = std::visit(VariantToFloatVisitor(),
250 findScaleFactor->second);
251 // scaleFactor is used in division
252 if (scaleFactor == 0.0F)
253 {
254 scaleFactor = 1.0;
255 }
256 }
257
258 float pollRate =
259 getPollRate(baseConfiguration->second, pollRateDefault);
260 PowerState readState = getPowerState(baseConfiguration->second);
261
262 auto& sensor = sensors[sensorName];
263 sensor = nullptr;
264
265 std::optional<BridgeGpio> bridgeGpio;
266 for (const auto& [key, cfgMap] : *sensorData)
267 {
268 if (key.find("BridgeGpio") != std::string::npos)
269 {
270 auto findName = cfgMap.find("Name");
271 if (findName != cfgMap.end())
272 {
273 std::string gpioName = std::visit(
274 VariantToStringVisitor(), findName->second);
275
276 int polarity = gpiod::line::ACTIVE_HIGH;
277 auto findPolarity = cfgMap.find("Polarity");
278 if (findPolarity != cfgMap.end())
279 {
280 if (std::string("Low") ==
281 std::visit(VariantToStringVisitor(),
282 findPolarity->second))
283 {
284 polarity = gpiod::line::ACTIVE_LOW;
285 }
286 }
287
288 float setupTime = gpioBridgeSetupTimeDefault;
289 auto findSetupTime = cfgMap.find("SetupTime");
290 if (findSetupTime != cfgMap.end())
291 {
292 setupTime = std::visit(VariantToFloatVisitor(),
293 findSetupTime->second);
294 }
295
296 bridgeGpio =
297 BridgeGpio(gpioName, polarity, setupTime);
298 }
299
300 break;
301 }
302 }
303
304 sensor = std::make_shared<ADCSensor>(
305 path.string(), objectServer, dbusConnection, io, sensorName,
306 std::move(sensorThresholds), scaleFactor, pollRate,
307 readState, *interfacePath, std::move(bridgeGpio));
308 sensor->setupRead();
309 }
310 });
311
312 getter->getConfiguration(
313 std::vector<std::string>{sensorTypes.begin(), sensorTypes.end()});
314 }
315
main()316 int main()
317 {
318 boost::asio::io_context io;
319 auto systemBus = std::make_shared<sdbusplus::asio::connection>(io);
320 sdbusplus::asio::object_server objectServer(systemBus, true);
321 objectServer.add_manager("/xyz/openbmc_project/sensors");
322
323 systemBus->request_name("xyz.openbmc_project.ADCSensor");
324 boost::container::flat_map<std::string, std::shared_ptr<ADCSensor>> sensors;
325 auto sensorsChanged =
326 std::make_shared<boost::container::flat_set<std::string>>();
327
328 boost::asio::post(io, [&]() {
329 createSensors(io, objectServer, sensors, systemBus, nullptr,
330 UpdateType::init);
331 });
332
333 boost::asio::steady_timer filterTimer(io);
334 std::function<void(sdbusplus::message_t&)> eventHandler =
335 [&](sdbusplus::message_t& message) {
336 if (message.is_method_error())
337 {
338 lg2::error("callback method error");
339 return;
340 }
341 sensorsChanged->insert(message.get_path());
342 // this implicitly cancels the timer
343 filterTimer.expires_after(std::chrono::seconds(1));
344
345 filterTimer.async_wait([&](const boost::system::error_code& ec) {
346 if (ec == boost::asio::error::operation_aborted)
347 {
348 /* we were canceled*/
349 return;
350 }
351 if (ec)
352 {
353 lg2::error("timer error");
354 return;
355 }
356 createSensors(io, objectServer, sensors, systemBus,
357 sensorsChanged, UpdateType::init);
358 });
359 };
360
361 boost::asio::steady_timer cpuFilterTimer(io);
362 std::function<void(sdbusplus::message_t&)> cpuPresenceHandler =
363 [&](sdbusplus::message_t& message) {
364 std::string path = message.get_path();
365 boost::to_lower(path);
366
367 sdbusplus::message::object_path cpuPath(path);
368 std::string cpuName = cpuPath.filename();
369 if (!cpuName.starts_with("cpu"))
370 {
371 return; // not interested
372 }
373 size_t index = 0;
374 try
375 {
376 index = std::stoi(path.substr(path.size() - 1));
377 }
378 catch (const std::invalid_argument&)
379 {
380 lg2::error("Found invalid path: '{PATH}'", "PATH", path);
381 return;
382 }
383
384 std::string objectName;
385 boost::container::flat_map<std::string, std::variant<bool>> values;
386 message.read(objectName, values);
387 auto findPresence = values.find("Present");
388 if (findPresence != values.end())
389 {
390 cpuPresence[index] = std::get<bool>(findPresence->second);
391 }
392
393 // this implicitly cancels the timer
394 cpuFilterTimer.expires_after(std::chrono::seconds(1));
395
396 cpuFilterTimer.async_wait([&](const boost::system::error_code& ec) {
397 if (ec == boost::asio::error::operation_aborted)
398 {
399 /* we were canceled*/
400 return;
401 }
402 if (ec)
403 {
404 lg2::error("timer error");
405 return;
406 }
407 createSensors(io, objectServer, sensors, systemBus, nullptr,
408 UpdateType::cpuPresenceChange);
409 });
410 };
411
412 std::vector<std::unique_ptr<sdbusplus::bus::match_t>> matches =
413 setupPropertiesChangedMatches(*systemBus, sensorTypes, eventHandler);
414 matches.emplace_back(std::make_unique<sdbusplus::bus::match_t>(
415 static_cast<sdbusplus::bus_t&>(*systemBus),
416 "type='signal',member='PropertiesChanged',path_namespace='" +
417 std::string(cpuInventoryPath) +
418 "',arg0namespace='xyz.openbmc_project.Inventory.Item'",
419 cpuPresenceHandler));
420
421 setupManufacturingModeMatch(*systemBus);
422 io.run();
423 }
424