xref: /openbmc/dbus-sensors/src/adc/ADCSensorMain.cpp (revision 89be6147e5a7ffa86d88d8f3e27eba8eb2c3a9da)
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