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