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