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 "DeviceMgmt.hpp" 18 #include "HwmonTempSensor.hpp" 19 #include "SensorPaths.hpp" 20 #include "Thresholds.hpp" 21 #include "Utils.hpp" 22 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 <sdbusplus/asio/connection.hpp> 30 #include <sdbusplus/asio/object_server.hpp> 31 #include <sdbusplus/bus.hpp> 32 #include <sdbusplus/bus/match.hpp> 33 #include <sdbusplus/message.hpp> 34 #include <sdbusplus/message/native_types.hpp> 35 36 #include <algorithm> 37 #include <array> 38 #include <chrono> 39 #include <cstddef> 40 #include <cstdint> 41 #include <filesystem> 42 #include <functional> 43 #include <ios> 44 #include <iostream> 45 #include <memory> 46 #include <optional> 47 #include <regex> 48 #include <string> 49 #include <system_error> 50 #include <utility> 51 #include <variant> 52 #include <vector> 53 54 static constexpr float pollRateDefault = 0.5; 55 56 static constexpr double maxValuePressure = 120000; // Pascals 57 static constexpr double minValuePressure = 30000; // Pascals 58 59 static constexpr double maxValueRelativeHumidity = 100; // PercentRH 60 static constexpr double minValueRelativeHumidity = 0; // PercentRH 61 62 static constexpr double maxValueTemperature = 127; // DegreesC 63 static constexpr double minValueTemperature = -128; // DegreesC 64 65 namespace fs = std::filesystem; 66 67 static const I2CDeviceTypeMap sensorTypes{ 68 {"ADM1021", I2CDeviceType{"adm1021", true}}, 69 {"DPS310", I2CDeviceType{"dps310", false}}, 70 {"EMC1403", I2CDeviceType{"emc1403", true}}, 71 {"EMC1412", I2CDeviceType{"emc1412", true}}, 72 {"EMC1413", I2CDeviceType{"emc1413", true}}, 73 {"EMC1414", I2CDeviceType{"emc1414", true}}, 74 {"HDC1080", I2CDeviceType{"hdc1080", false}}, 75 {"JC42", I2CDeviceType{"jc42", true}}, 76 {"LM75A", I2CDeviceType{"lm75a", true}}, 77 {"LM95234", I2CDeviceType{"lm95234", true}}, 78 {"MAX31725", I2CDeviceType{"max31725", true}}, 79 {"MAX31730", I2CDeviceType{"max31730", true}}, 80 {"MAX6581", I2CDeviceType{"max6581", true}}, 81 {"MAX6654", I2CDeviceType{"max6654", true}}, 82 {"MAX6639", I2CDeviceType{"max6639", true}}, 83 {"MCP9600", I2CDeviceType{"mcp9600", false}}, 84 {"NCT6779", I2CDeviceType{"nct6779", true}}, 85 {"NCT7802", I2CDeviceType{"nct7802", true}}, 86 {"PT5161L", I2CDeviceType{"pt5161l", true}}, 87 {"SBTSI", I2CDeviceType{"sbtsi", true}}, 88 {"SI7020", I2CDeviceType{"si7020", false}}, 89 {"TMP100", I2CDeviceType{"tmp100", true}}, 90 {"TMP112", I2CDeviceType{"tmp112", true}}, 91 {"TMP175", I2CDeviceType{"tmp175", true}}, 92 {"TMP421", I2CDeviceType{"tmp421", true}}, 93 {"TMP441", I2CDeviceType{"tmp441", true}}, 94 {"TMP461", I2CDeviceType{"tmp461", true}}, 95 {"TMP464", I2CDeviceType{"tmp464", true}}, 96 {"TMP75", I2CDeviceType{"tmp75", true}}, 97 {"W83773G", I2CDeviceType{"w83773g", true}}, 98 }; 99 100 static struct SensorParams 101 getSensorParameters(const std::filesystem::path& path) 102 { 103 // offset is to default to 0 and scale to 1, see lore 104 // https://lore.kernel.org/linux-iio/5c79425f-6e88-36b6-cdfe-4080738d039f@metafoo.de/ 105 struct SensorParams tmpSensorParameters = { 106 .minValue = minValueTemperature, 107 .maxValue = maxValueTemperature, 108 .offsetValue = 0.0, 109 .scaleValue = 1.0, 110 .units = sensor_paths::unitDegreesC, 111 .typeName = "temperature"}; 112 113 // For IIO RAW sensors we get a raw_value, an offset, and scale 114 // to compute the value = (raw_value + offset) * scale 115 // with a _raw IIO device we need to get the 116 // offsetValue and scaleValue from the driver 117 // these are used to compute the reading in 118 // units that have yet to be scaled for D-Bus. 119 const std::string pathStr = path.string(); 120 if (pathStr.ends_with("_raw")) 121 { 122 std::string pathOffsetStr = pathStr.substr(0, pathStr.size() - 4) + 123 "_offset"; 124 std::optional<double> tmpOffsetValue = readFile(pathOffsetStr, 1.0); 125 // In case there is nothing to read skip this device 126 // This is not an error condition see lore 127 // https://lore.kernel.org/linux-iio/5c79425f-6e88-36b6-cdfe-4080738d039f@metafoo.de/ 128 if (tmpOffsetValue) 129 { 130 tmpSensorParameters.offsetValue = *tmpOffsetValue; 131 } 132 133 std::string pathScaleStr = pathStr.substr(0, pathStr.size() - 4) + 134 "_scale"; 135 std::optional<double> tmpScaleValue = readFile(pathScaleStr, 1.0); 136 // In case there is nothing to read skip this device 137 // This is not an error condition see lore 138 // https://lore.kernel.org/linux-iio/5c79425f-6e88-36b6-cdfe-4080738d039f@metafoo.de/ 139 if (tmpScaleValue) 140 { 141 tmpSensorParameters.scaleValue = *tmpScaleValue; 142 } 143 } 144 145 // Temperatures are read in milli degrees Celsius, we need 146 // degrees Celsius. Pressures are read in kilopascal, we need 147 // Pascals. On D-Bus for Open BMC we use the International 148 // System of Units without prefixes. Links to the kernel 149 // documentation: 150 // https://www.kernel.org/doc/Documentation/hwmon/sysfs-interface 151 // https://www.kernel.org/doc/Documentation/ABI/testing/sysfs-bus-iio 152 if (path.filename() == "in_pressure_input" || 153 path.filename() == "in_pressure_raw") 154 { 155 tmpSensorParameters.minValue = minValuePressure; 156 tmpSensorParameters.maxValue = maxValuePressure; 157 // Pressures are read in kilopascal, we need Pascals. 158 tmpSensorParameters.scaleValue *= 1000.0; 159 tmpSensorParameters.typeName = "pressure"; 160 tmpSensorParameters.units = sensor_paths::unitPascals; 161 } 162 else if (path.filename() == "in_humidityrelative_input" || 163 path.filename() == "in_humidityrelative_raw") 164 { 165 tmpSensorParameters.minValue = minValueRelativeHumidity; 166 tmpSensorParameters.maxValue = maxValueRelativeHumidity; 167 // Relative Humidity are read in milli-percent, we need percent. 168 tmpSensorParameters.scaleValue *= 0.001; 169 tmpSensorParameters.typeName = "humidity"; 170 tmpSensorParameters.units = sensor_paths::unitPercentRH; 171 } 172 else 173 { 174 // Temperatures are read in milli degrees Celsius, 175 // we need degrees Celsius. 176 tmpSensorParameters.scaleValue *= 0.001; 177 } 178 179 return tmpSensorParameters; 180 } 181 182 struct SensorConfigKey 183 { 184 uint64_t bus; 185 uint64_t addr; 186 bool operator<(const SensorConfigKey& other) const 187 { 188 if (bus != other.bus) 189 { 190 return bus < other.bus; 191 } 192 return addr < other.addr; 193 } 194 }; 195 196 struct SensorConfig 197 { 198 std::string sensorPath; 199 SensorData sensorData; 200 std::string interface; 201 SensorBaseConfigMap config; 202 std::vector<std::string> name; 203 }; 204 205 using SensorConfigMap = 206 boost::container::flat_map<SensorConfigKey, SensorConfig>; 207 208 static SensorConfigMap 209 buildSensorConfigMap(const ManagedObjectType& sensorConfigs) 210 { 211 SensorConfigMap configMap; 212 for (const auto& [path, cfgData] : sensorConfigs) 213 { 214 for (const auto& [intf, cfg] : cfgData) 215 { 216 auto busCfg = cfg.find("Bus"); 217 auto addrCfg = cfg.find("Address"); 218 if ((busCfg == cfg.end()) || (addrCfg == cfg.end())) 219 { 220 continue; 221 } 222 223 if ((std::get_if<uint64_t>(&busCfg->second) == nullptr) || 224 (std::get_if<uint64_t>(&addrCfg->second) == nullptr)) 225 { 226 std::cerr << path.str << " Bus or Address invalid\n"; 227 continue; 228 } 229 230 std::vector<std::string> hwmonNames; 231 auto nameCfg = cfg.find("Name"); 232 if (nameCfg != cfg.end()) 233 { 234 hwmonNames.push_back(std::get<std::string>(nameCfg->second)); 235 size_t i = 1; 236 while (true) 237 { 238 auto sensorNameCfg = cfg.find("Name" + std::to_string(i)); 239 if (sensorNameCfg == cfg.end()) 240 { 241 break; 242 } 243 hwmonNames.push_back( 244 std::get<std::string>(sensorNameCfg->second)); 245 i++; 246 } 247 } 248 249 SensorConfigKey key = {std::get<uint64_t>(busCfg->second), 250 std::get<uint64_t>(addrCfg->second)}; 251 SensorConfig val = {path.str, cfgData, intf, cfg, hwmonNames}; 252 253 auto [it, inserted] = configMap.emplace(key, std::move(val)); 254 if (!inserted) 255 { 256 std::cerr << path.str << ": ignoring duplicate entry for {" 257 << key.bus << ", 0x" << std::hex << key.addr 258 << std::dec << "}\n"; 259 } 260 } 261 } 262 return configMap; 263 } 264 265 void createSensors( 266 boost::asio::io_context& io, sdbusplus::asio::object_server& objectServer, 267 boost::container::flat_map<std::string, std::shared_ptr<HwmonTempSensor>>& 268 sensors, 269 std::shared_ptr<sdbusplus::asio::connection>& dbusConnection, 270 const std::shared_ptr<boost::container::flat_set<std::string>>& 271 sensorsChanged, 272 bool activateOnly) 273 { 274 auto getter = std::make_shared<GetSensorConfiguration>( 275 dbusConnection, 276 [&io, &objectServer, &sensors, &dbusConnection, sensorsChanged, 277 activateOnly](const ManagedObjectType& sensorConfigurations) { 278 bool firstScan = sensorsChanged == nullptr; 279 280 SensorConfigMap configMap = buildSensorConfigMap(sensorConfigurations); 281 282 auto devices = instantiateDevices(sensorConfigurations, sensors, 283 sensorTypes); 284 285 // IIO _raw devices look like this on sysfs: 286 // /sys/bus/iio/devices/iio:device0/in_temp_raw 287 // /sys/bus/iio/devices/iio:device0/in_temp_offset 288 // /sys/bus/iio/devices/iio:device0/in_temp_scale 289 // 290 // Other IIO devices look like this on sysfs: 291 // /sys/bus/iio/devices/iio:device1/in_temp_input 292 // /sys/bus/iio/devices/iio:device1/in_pressure_input 293 std::vector<fs::path> paths; 294 fs::path root("/sys/bus/iio/devices"); 295 findFiles(root, R"(in_temp\d*_(input|raw))", paths); 296 findFiles(root, R"(in_pressure\d*_(input|raw))", paths); 297 findFiles(root, R"(in_humidityrelative\d*_(input|raw))", paths); 298 findFiles(fs::path("/sys/class/hwmon"), R"(temp\d+_input)", paths); 299 300 // iterate through all found temp and pressure sensors, 301 // and try to match them with configuration 302 for (auto& path : paths) 303 { 304 std::smatch match; 305 const std::string pathStr = path.string(); 306 auto directory = path.parent_path(); 307 fs::path device; 308 309 std::string deviceName; 310 std::error_code ec; 311 if (pathStr.starts_with("/sys/bus/iio/devices")) 312 { 313 device = fs::canonical(directory, ec); 314 if (ec) 315 { 316 std::cerr << "Fail to find device in path [" << pathStr 317 << "]\n"; 318 continue; 319 } 320 deviceName = device.parent_path().stem(); 321 } 322 else 323 { 324 device = fs::canonical(directory / "device", ec); 325 if (ec) 326 { 327 std::cerr << "Fail to find device in path [" << pathStr 328 << "]\n"; 329 continue; 330 } 331 deviceName = device.stem(); 332 } 333 334 uint64_t bus = 0; 335 uint64_t addr = 0; 336 if (!getDeviceBusAddr(deviceName, bus, addr)) 337 { 338 continue; 339 } 340 341 auto thisSensorParameters = getSensorParameters(path); 342 auto findSensorCfg = configMap.find({bus, addr}); 343 if (findSensorCfg == configMap.end()) 344 { 345 continue; 346 } 347 348 const std::string& interfacePath = findSensorCfg->second.sensorPath; 349 auto findI2CDev = devices.find(interfacePath); 350 351 std::shared_ptr<I2CDevice> i2cDev; 352 if (findI2CDev != devices.end()) 353 { 354 // If we're only looking to activate newly-instantiated i2c 355 // devices and this sensor's underlying device was already there 356 // before this call, there's nothing more to do here. 357 if (activateOnly && !findI2CDev->second.second) 358 { 359 continue; 360 } 361 i2cDev = findI2CDev->second.first; 362 } 363 364 const SensorData& sensorData = findSensorCfg->second.sensorData; 365 std::string sensorType = findSensorCfg->second.interface; 366 auto pos = sensorType.find_last_of('.'); 367 if (pos != std::string::npos) 368 { 369 sensorType = sensorType.substr(pos + 1); 370 } 371 const SensorBaseConfigMap& baseConfigMap = 372 findSensorCfg->second.config; 373 std::vector<std::string>& hwmonName = findSensorCfg->second.name; 374 375 // Temperature has "Name", pressure has "Name1" 376 auto findSensorName = baseConfigMap.find("Name"); 377 int index = 1; 378 if (thisSensorParameters.typeName == "pressure" || 379 thisSensorParameters.typeName == "humidity") 380 { 381 findSensorName = baseConfigMap.find("Name1"); 382 index = 2; 383 } 384 385 if (findSensorName == baseConfigMap.end()) 386 { 387 std::cerr << "could not determine configuration name for " 388 << deviceName << "\n"; 389 continue; 390 } 391 std::string sensorName = 392 std::get<std::string>(findSensorName->second); 393 // on rescans, only update sensors we were signaled by 394 auto findSensor = sensors.find(sensorName); 395 if (!firstScan && findSensor != sensors.end()) 396 { 397 bool found = false; 398 auto it = sensorsChanged->begin(); 399 while (it != sensorsChanged->end()) 400 { 401 if (it->ends_with(findSensor->second->name)) 402 { 403 it = sensorsChanged->erase(it); 404 findSensor->second = nullptr; 405 found = true; 406 break; 407 } 408 ++it; 409 } 410 if (!found) 411 { 412 continue; 413 } 414 } 415 416 std::vector<thresholds::Threshold> sensorThresholds; 417 418 if (!parseThresholdsFromConfig(sensorData, sensorThresholds, 419 nullptr, &index)) 420 { 421 std::cerr << "error populating thresholds for " << sensorName 422 << " index " << index << "\n"; 423 } 424 425 float pollRate = getPollRate(baseConfigMap, pollRateDefault); 426 PowerState readState = getPowerState(baseConfigMap); 427 428 auto permitSet = getPermitSet(baseConfigMap); 429 auto& sensor = sensors[sensorName]; 430 if (!activateOnly) 431 { 432 sensor = nullptr; 433 } 434 auto hwmonFile = getFullHwmonFilePath(directory.string(), "temp1", 435 permitSet); 436 if (pathStr.starts_with("/sys/bus/iio/devices")) 437 { 438 hwmonFile = pathStr; 439 } 440 if (hwmonFile) 441 { 442 if (sensor != nullptr) 443 { 444 sensor->activate(*hwmonFile, i2cDev); 445 } 446 else 447 { 448 sensor = std::make_shared<HwmonTempSensor>( 449 *hwmonFile, sensorType, objectServer, dbusConnection, 450 io, sensorName, std::move(sensorThresholds), 451 thisSensorParameters, pollRate, interfacePath, 452 readState, i2cDev); 453 sensor->setupRead(); 454 } 455 } 456 hwmonName.erase( 457 remove(hwmonName.begin(), hwmonName.end(), sensorName), 458 hwmonName.end()); 459 460 // Looking for keys like "Name1" for temp2_input, 461 // "Name2" for temp3_input, etc. 462 int i = 0; 463 while (true) 464 { 465 ++i; 466 auto findKey = baseConfigMap.find("Name" + std::to_string(i)); 467 if (findKey == baseConfigMap.end()) 468 { 469 break; 470 } 471 std::string sensorName = std::get<std::string>(findKey->second); 472 hwmonFile = getFullHwmonFilePath(directory.string(), 473 "temp" + std::to_string(i + 1), 474 permitSet); 475 if (pathStr.starts_with("/sys/bus/iio/devices")) 476 { 477 continue; 478 } 479 if (hwmonFile) 480 { 481 // To look up thresholds for these additional sensors, 482 // match on the Index property in the threshold data 483 // where the index comes from the sysfs file we're on, 484 // i.e. index = 2 for temp2_input. 485 int index = i + 1; 486 std::vector<thresholds::Threshold> thresholds; 487 488 if (!parseThresholdsFromConfig(sensorData, thresholds, 489 nullptr, &index)) 490 { 491 std::cerr << "error populating thresholds for " 492 << sensorName << " index " << index << "\n"; 493 } 494 495 auto& sensor = sensors[sensorName]; 496 if (!activateOnly) 497 { 498 sensor = nullptr; 499 } 500 501 if (sensor != nullptr) 502 { 503 sensor->activate(*hwmonFile, i2cDev); 504 } 505 else 506 { 507 sensor = std::make_shared<HwmonTempSensor>( 508 *hwmonFile, sensorType, objectServer, 509 dbusConnection, io, sensorName, 510 std::move(thresholds), thisSensorParameters, 511 pollRate, interfacePath, readState, i2cDev); 512 sensor->setupRead(); 513 } 514 } 515 516 hwmonName.erase( 517 remove(hwmonName.begin(), hwmonName.end(), sensorName), 518 hwmonName.end()); 519 } 520 if (hwmonName.empty()) 521 { 522 configMap.erase(findSensorCfg); 523 } 524 } 525 }); 526 std::vector<std::string> types(sensorTypes.size()); 527 for (const auto& [type, dt] : sensorTypes) 528 { 529 types.push_back(type); 530 } 531 getter->getConfiguration(types); 532 } 533 534 void interfaceRemoved( 535 sdbusplus::message_t& message, 536 boost::container::flat_map<std::string, std::shared_ptr<HwmonTempSensor>>& 537 sensors) 538 { 539 if (message.is_method_error()) 540 { 541 std::cerr << "interfacesRemoved callback method error\n"; 542 return; 543 } 544 545 sdbusplus::message::object_path path; 546 std::vector<std::string> interfaces; 547 548 message.read(path, interfaces); 549 550 // If the xyz.openbmc_project.Confguration.X interface was removed 551 // for one or more sensors, delete those sensor objects. 552 auto sensorIt = sensors.begin(); 553 while (sensorIt != sensors.end()) 554 { 555 if (sensorIt->second && (sensorIt->second->configurationPath == path) && 556 (std::find(interfaces.begin(), interfaces.end(), 557 sensorIt->second->configInterface) != interfaces.end())) 558 { 559 sensorIt = sensors.erase(sensorIt); 560 } 561 else 562 { 563 sensorIt++; 564 } 565 } 566 } 567 568 static void powerStateChanged( 569 PowerState type, bool newState, 570 boost::container::flat_map<std::string, std::shared_ptr<HwmonTempSensor>>& 571 sensors, 572 boost::asio::io_context& io, sdbusplus::asio::object_server& objectServer, 573 std::shared_ptr<sdbusplus::asio::connection>& dbusConnection) 574 { 575 if (newState) 576 { 577 createSensors(io, objectServer, sensors, dbusConnection, nullptr, true); 578 } 579 else 580 { 581 for (auto& [path, sensor] : sensors) 582 { 583 if (sensor != nullptr && sensor->readState == type) 584 { 585 sensor->deactivate(); 586 } 587 } 588 } 589 } 590 591 int main() 592 { 593 boost::asio::io_context io; 594 auto systemBus = std::make_shared<sdbusplus::asio::connection>(io); 595 sdbusplus::asio::object_server objectServer(systemBus, true); 596 objectServer.add_manager("/xyz/openbmc_project/sensors"); 597 systemBus->request_name("xyz.openbmc_project.HwmonTempSensor"); 598 599 boost::container::flat_map<std::string, std::shared_ptr<HwmonTempSensor>> 600 sensors; 601 auto sensorsChanged = 602 std::make_shared<boost::container::flat_set<std::string>>(); 603 604 auto powerCallBack = [&sensors, &io, &objectServer, 605 &systemBus](PowerState type, bool state) { 606 powerStateChanged(type, state, sensors, io, objectServer, systemBus); 607 }; 608 setupPowerMatchCallback(systemBus, powerCallBack); 609 610 boost::asio::post(io, [&]() { 611 createSensors(io, objectServer, sensors, systemBus, nullptr, false); 612 }); 613 614 boost::asio::steady_timer filterTimer(io); 615 std::function<void(sdbusplus::message_t&)> eventHandler = 616 [&](sdbusplus::message_t& message) { 617 if (message.is_method_error()) 618 { 619 std::cerr << "callback method error\n"; 620 return; 621 } 622 sensorsChanged->insert(message.get_path()); 623 // this implicitly cancels the timer 624 filterTimer.expires_after(std::chrono::seconds(1)); 625 626 filterTimer.async_wait([&](const boost::system::error_code& ec) { 627 if (ec == boost::asio::error::operation_aborted) 628 { 629 /* we were canceled*/ 630 return; 631 } 632 if (ec) 633 { 634 std::cerr << "timer error\n"; 635 return; 636 } 637 createSensors(io, objectServer, sensors, systemBus, sensorsChanged, 638 false); 639 }); 640 }; 641 642 std::vector<std::unique_ptr<sdbusplus::bus::match_t>> matches = 643 setupPropertiesChangedMatches(*systemBus, sensorTypes, eventHandler); 644 setupManufacturingModeMatch(*systemBus); 645 646 // Watch for entity-manager to remove configuration interfaces 647 // so the corresponding sensors can be removed. 648 auto ifaceRemovedMatch = std::make_unique<sdbusplus::bus::match_t>( 649 static_cast<sdbusplus::bus_t&>(*systemBus), 650 "type='signal',member='InterfacesRemoved',arg0path='" + 651 std::string(inventoryPath) + "/'", 652 [&sensors](sdbusplus::message_t& msg) { 653 interfaceRemoved(msg, sensors); 654 }); 655 656 matches.emplace_back(std::move(ifaceRemovedMatch)); 657 658 io.run(); 659 } 660