1 /* 2 * SPDX-FileCopyrightText: Copyright (c) 2022-2025 NVIDIA CORPORATION & 3 * AFFILIATES. All rights reserved. 4 * SPDX-License-Identifier: Apache-2.0 5 */ 6 7 #include "SmbpbiSensor.hpp" 8 9 #include "SensorPaths.hpp" 10 #include "Thresholds.hpp" 11 #include "Utils.hpp" 12 #include "sensor.hpp" 13 14 #include <linux/i2c.h> 15 16 #include <boost/asio/error.hpp> 17 #include <boost/asio/io_context.hpp> 18 #include <boost/asio/post.hpp> 19 #include <boost/container/flat_map.hpp> 20 #include <phosphor-logging/lg2.hpp> 21 #include <sdbusplus/asio/connection.hpp> 22 #include <sdbusplus/asio/object_server.hpp> 23 #include <sdbusplus/bus.hpp> 24 #include <sdbusplus/bus/match.hpp> 25 #include <sdbusplus/message.hpp> 26 27 #include <array> 28 #include <chrono> 29 #include <cstdint> 30 #include <cstring> 31 #include <functional> 32 #include <limits> 33 #include <memory> 34 #include <string> 35 #include <utility> 36 #include <vector> 37 38 extern "C" 39 { 40 #include <linux/i2c-dev.h> 41 #include <sys/ioctl.h> 42 } 43 44 constexpr const bool debug = false; 45 46 constexpr const char* configInterface = 47 "xyz.openbmc_project.Configuration.SmbpbiVirtualEeprom"; 48 constexpr const char* sensorRootPath = "/xyz/openbmc_project/sensors/"; 49 constexpr const char* objectType = "SmbpbiVirtualEeprom"; 50 51 boost::container::flat_map<std::string, std::unique_ptr<SmbpbiSensor>> sensors; 52 53 SmbpbiSensor::SmbpbiSensor( 54 std::shared_ptr<sdbusplus::asio::connection>& conn, 55 boost::asio::io_context& io, const std::string& sensorName, 56 const std::string& sensorConfiguration, const std::string& objType, 57 sdbusplus::asio::object_server& objectServer, 58 std::vector<thresholds::Threshold>&& thresholdData, uint8_t busId, 59 uint8_t addr, uint16_t offset, std::string& sensorUnits, 60 std::string& valueType, size_t pollTime, double minVal, double maxVal, 61 std::string& path) : 62 Sensor(escapeName(sensorName), std::move(thresholdData), 63 sensorConfiguration, objType, false, false, maxVal, minVal, conn), 64 busId(busId), addr(addr), offset(offset), sensorUnits(sensorUnits), 65 valueType(valueType), objectServer(objectServer), 66 inputDev(io, path, boost::asio::random_access_file::read_only), 67 waitTimer(io), pollRateSecond(pollTime) 68 { 69 sensorType = sensor_paths::getPathForUnits(sensorUnits); 70 std::string sensorPath = sensorRootPath + sensorType + "/"; 71 72 sensorInterface = 73 objectServer.add_interface(sensorPath + name, sensorValueInterface); 74 75 for (const auto& threshold : thresholds) 76 { 77 std::string interface = thresholds::getInterface(threshold.level); 78 thresholdInterfaces[static_cast<size_t>(threshold.level)] = 79 objectServer.add_interface(sensorPath + name, interface); 80 } 81 association = 82 objectServer.add_interface(sensorPath + name, association::interface); 83 84 if (sensorType == "temperature") 85 { 86 setInitialProperties(sensor_paths::unitDegreesC); 87 } 88 else if (sensorType == "power") 89 { 90 setInitialProperties(sensor_paths::unitWatts); 91 } 92 else if (sensorType == "energy") 93 { 94 setInitialProperties(sensor_paths::unitJoules); 95 } 96 else if (sensorType == "voltage") 97 { 98 setInitialProperties(sensor_paths::unitVolts); 99 } 100 else 101 { 102 lg2::error("no sensor type found"); 103 } 104 } 105 106 SmbpbiSensor::~SmbpbiSensor() 107 { 108 inputDev.close(); 109 waitTimer.cancel(); 110 for (const auto& iface : thresholdInterfaces) 111 { 112 objectServer.remove_interface(iface); 113 } 114 objectServer.remove_interface(sensorInterface); 115 objectServer.remove_interface(association); 116 } 117 118 void SmbpbiSensor::init() 119 { 120 read(); 121 } 122 123 void SmbpbiSensor::checkThresholds() 124 { 125 thresholds::checkThresholds(this); 126 } 127 128 double SmbpbiSensor::convert2Temp(const uint8_t* raw) 129 { 130 // Temp data is encoded in SMBPBI format. The 3 MSBs denote 131 // the integer portion, LSB is an encoded fraction. 132 // this automatic convert to int (two's complement integer) 133 int32_t intg = (raw[3] << 24 | raw[2] << 16 | raw[1] << 8 | raw[0]); 134 uint8_t frac = uint8_t(raw[0]); 135 // shift operation on a int keeps the sign in two's complement 136 intg >>= 8; 137 138 double temp = 0; 139 if (intg > 0) 140 { 141 temp = double(intg) + double(frac / 256.0); 142 } 143 else 144 { 145 temp = double(intg) - double(frac / 256.0); 146 } 147 148 return temp; 149 } 150 151 double SmbpbiSensor::convert2Power(const uint8_t* raw) 152 { 153 // Power data is encoded as a 4-byte unsigned integer 154 uint32_t val = (raw[3] << 24) + (raw[2] << 16) + (raw[1] << 8) + raw[0]; 155 156 // mWatts to Watts 157 double power = static_cast<double>(val) / 1000; 158 159 return power; 160 } 161 162 int SmbpbiSensor::i2cReadDataBytesDouble(double& reading) 163 { 164 constexpr int length = 165 i2CReadLenValues[static_cast<size_t>(I2C_READ_LEN_INDEX::FLOAT64)]; 166 167 static_assert(length == sizeof(reading), "Unsupported arch"); 168 169 std::array<uint8_t, length> buf{}; 170 int ret = i2cReadDataBytes(buf.data(), length); 171 if (ret < 0) 172 { 173 return ret; 174 } 175 // there is no value updated from HMC if reading data is all 0xff 176 // Return NaN since reading is already a double 177 if (checkInvalidReading(buf.data(), length)) 178 { 179 reading = std::numeric_limits<double>::quiet_NaN(); 180 return 0; 181 } 182 uint64_t tempd = 0; 183 for (int byteI = 0; byteI < length; byteI++) 184 { 185 tempd |= static_cast<uint64_t>(buf[byteI]) << (8 * byteI); 186 } 187 std::memcpy(&reading, &tempd, sizeof(reading)); 188 189 return 0; 190 } 191 192 int SmbpbiSensor::i2cReadDataBytesUI64(uint64_t& reading) 193 { 194 constexpr int length = 195 i2CReadLenValues[static_cast<size_t>(I2C_READ_LEN_INDEX::UINT64)]; 196 197 static_assert(length == sizeof(reading), "Unsupported arch"); 198 199 std::array<uint8_t, length> buf{}; 200 int ret = i2cReadDataBytes(buf.data(), length); 201 if (ret < 0) 202 { 203 return ret; 204 } 205 reading = 0; 206 for (int byteI = 0; byteI < length; byteI++) 207 { 208 reading |= static_cast<uint64_t>(buf[byteI]) << (8 * byteI); 209 } 210 return 0; 211 } 212 213 // Generic i2c Command to read bytes 214 int SmbpbiSensor::i2cReadDataBytes(uint8_t* reading, int length) 215 { 216 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg) 217 const int fd = inputDev.native_handle(); 218 if (fd < 0) 219 { 220 lg2::error(" unable to open i2c device on bus {BUS} err={FD}", "BUS", 221 busId, "FD", fd); 222 return -1; 223 } 224 225 unsigned long funcs = 0; 226 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg) 227 if (ioctl(fd, I2C_FUNCS, &funcs) < 0) 228 { 229 lg2::error(" I2C_FUNCS not supported"); 230 return -1; 231 } 232 233 int ret = 0; 234 struct i2c_rdwr_ioctl_data args = {nullptr, 0}; 235 std::array<struct i2c_msg, 2> msgs = { 236 {{0, 0, 0, nullptr}, {0, 0, 0, nullptr}}}; 237 std::array<uint8_t, 8> cmd{}; 238 239 args.msgs = msgs.data(); 240 args.nmsgs = msgs.size(); 241 242 msgs[0].addr = addr; 243 msgs[0].flags = 0; 244 msgs[0].buf = cmd.data(); 245 // handle two bytes offset 246 if (offset > 255) 247 { 248 msgs[0].len = 2; 249 msgs[0].buf[0] = offset >> 8; 250 msgs[0].buf[1] = offset & 0xFF; 251 } 252 else 253 { 254 msgs[0].len = 1; 255 msgs[0].buf[0] = offset & 0xFF; 256 } 257 258 msgs[1].addr = addr; 259 msgs[1].flags = I2C_M_RD; 260 msgs[1].len = length; 261 msgs[1].buf = reading; 262 263 // write offset 264 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg) 265 ret = ioctl(fd, I2C_RDWR, &args); 266 if (ret < 0) 267 { 268 return ret; 269 } 270 return 0; 271 } 272 273 int SmbpbiSensor::readRawEEPROMData(double& data) 274 { 275 uint64_t reading = 0; 276 int ret = i2cReadDataBytesUI64(reading); 277 if (ret < 0) 278 { 279 return ret; 280 } 281 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) 282 if (checkInvalidReading(reinterpret_cast<uint8_t*>(&reading), 283 sizeof(reading))) 284 { 285 data = std::numeric_limits<double>::quiet_NaN(); 286 return 0; 287 } 288 if (debug) 289 { 290 lg2::error("offset: {OFFSET} reading: {READING}", "OFFSET", offset, 291 "READING", reading); 292 } 293 if (sensorType == "temperature") 294 { 295 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) 296 data = convert2Temp(reinterpret_cast<uint8_t*>(&reading)); 297 } 298 else if (sensorType == "power") 299 { 300 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) 301 data = convert2Power(reinterpret_cast<uint8_t*>(&reading)); 302 } 303 else if (sensorType == "energy") 304 { 305 data = reading / 1000.0; // mJ to J (double) 306 } 307 else 308 { 309 data = reading; // Voltage 310 } 311 return 0; 312 } 313 314 int SmbpbiSensor::readFloat64EEPROMData(double& data) 315 { 316 double reading = 0; 317 int ret = i2cReadDataBytesDouble(reading); 318 if (ret < 0) 319 { 320 return ret; 321 } 322 data = reading; 323 return 0; 324 } 325 326 void SmbpbiSensor::waitReadCallback(const boost::system::error_code& ec) 327 { 328 if (ec == boost::asio::error::operation_aborted) 329 { 330 // we're being cancelled 331 return; 332 } 333 // read timer error 334 if (ec) 335 { 336 lg2::error("timer error"); 337 return; 338 } 339 double temp = 0; 340 341 int ret = 0; 342 // Sensor reading value types are sensor-specific. So, read 343 // and interpret sensor data based on it's value type. 344 if (valueType == "UINT64") 345 { 346 ret = readRawEEPROMData(temp); 347 } 348 else if (valueType == "FLOAT64") 349 { 350 ret = readFloat64EEPROMData(temp); 351 } 352 else 353 { 354 return; 355 } 356 357 if (ret >= 0) 358 { 359 if constexpr (debug) 360 { 361 lg2::error("Value update to {TEMP}", "TEMP", temp); 362 } 363 updateValue(temp); 364 } 365 else 366 { 367 lg2::error("Invalid read getRegsInfo"); 368 incrementError(); 369 } 370 read(); 371 } 372 373 void SmbpbiSensor::read() 374 { 375 size_t pollTime = getPollRate(); // in seconds 376 377 waitTimer.expires_after(std::chrono::seconds(pollTime)); 378 waitTimer.async_wait([this](const boost::system::error_code& ec) { 379 this->waitReadCallback(ec); 380 }); 381 } 382 383 static void createSensorCallback( 384 boost::system::error_code ec, const ManagedObjectType& resp, 385 boost::asio::io_context& io, sdbusplus::asio::object_server& objectServer, 386 std::shared_ptr<sdbusplus::asio::connection>& dbusConnection, 387 boost::container::flat_map<std::string, std::unique_ptr<SmbpbiSensor>>& 388 sensors) 389 { 390 if (ec) 391 { 392 lg2::error("Error contacting entity manager"); 393 return; 394 } 395 for (const auto& pathPair : resp) 396 { 397 for (const auto& entry : pathPair.second) 398 { 399 if (entry.first != configInterface) 400 { 401 continue; 402 } 403 std::string name = loadVariant<std::string>(entry.second, "Name"); 404 405 std::vector<thresholds::Threshold> sensorThresholds; 406 if (!parseThresholdsFromConfig(pathPair.second, sensorThresholds)) 407 { 408 lg2::error("error populating thresholds for {NAME}", "NAME", 409 name); 410 } 411 412 uint8_t busId = loadVariant<uint8_t>(entry.second, "Bus"); 413 414 uint8_t addr = loadVariant<uint8_t>(entry.second, "Address"); 415 416 uint16_t off = loadVariant<uint16_t>(entry.second, "ReadOffset"); 417 418 std::string sensorUnits = 419 loadVariant<std::string>(entry.second, "Units"); 420 421 std::string valueType = 422 loadVariant<std::string>(entry.second, "ValueType"); 423 if (valueType != "UINT64" && valueType != "FLOAT64") 424 { 425 lg2::error("Invalid ValueType for sensor: {NAME}", "NAME", 426 name); 427 break; 428 } 429 430 size_t rate = loadVariant<uint8_t>(entry.second, "PollRate"); 431 432 double minVal = loadVariant<double>(entry.second, "MinValue"); 433 434 double maxVal = loadVariant<double>(entry.second, "MaxValue"); 435 if constexpr (debug) 436 { 437 lg2::info("Configuration parsed for \n\t {CONF}\nwith\n" 438 "\tName: {NAME}\n" 439 "\tBus: {BUS}\n" 440 "\tAddress:{ADDR}\n" 441 "\tOffset: {OFF}\n" 442 "\tType : {TYPE}\n" 443 "\tValue Type : {VALUETYPE}\n" 444 "\tPollrate: {RATE}\n" 445 "\tMinValue: {MIN}\n" 446 "\tMaxValue: {MAX}\n", 447 "CONF", entry.first, "NAME", name, "BUS", 448 static_cast<int>(busId), "ADDR", 449 static_cast<int>(addr), "OFF", static_cast<int>(off), 450 "UNITS", sensorUnits, "VALUETYPE", valueType, "RATE", 451 rate, "MIN", minVal, "MAX", maxVal); 452 } 453 454 auto& sensor = sensors[name]; 455 sensor = nullptr; 456 457 std::string path = "/dev/i2c-" + std::to_string(busId); 458 459 sensor = std::make_unique<SmbpbiSensor>( 460 dbusConnection, io, name, pathPair.first, objectType, 461 objectServer, std::move(sensorThresholds), busId, addr, off, 462 sensorUnits, valueType, rate, minVal, maxVal, path); 463 464 sensor->init(); 465 } 466 } 467 } 468 469 void createSensors( 470 boost::asio::io_context& io, sdbusplus::asio::object_server& objectServer, 471 boost::container::flat_map<std::string, std::unique_ptr<SmbpbiSensor>>& 472 sensors, 473 std::shared_ptr<sdbusplus::asio::connection>& dbusConnection) 474 { 475 if (!dbusConnection) 476 { 477 lg2::error("Connection not created"); 478 return; 479 } 480 481 dbusConnection->async_method_call( 482 [&io, &objectServer, &dbusConnection, &sensors]( 483 boost::system::error_code ec, const ManagedObjectType& resp) { 484 createSensorCallback(ec, resp, io, objectServer, dbusConnection, 485 sensors); 486 }, 487 entityManagerName, "/xyz/openbmc_project/inventory", 488 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 489 } 490 491 int main() 492 { 493 boost::asio::io_context io; 494 auto systemBus = std::make_shared<sdbusplus::asio::connection>(io); 495 sdbusplus::asio::object_server objectServer(systemBus, true); 496 objectServer.add_manager("/xyz/openbmc_project/sensors"); 497 systemBus->request_name("xyz.openbmc_project.SMBPBI"); 498 499 boost::asio::post(io, [&]() { 500 createSensors(io, objectServer, sensors, systemBus); 501 }); 502 503 boost::asio::steady_timer configTimer(io); 504 505 std::function<void(sdbusplus::message_t&)> eventHandler = 506 [&](sdbusplus::message_t&) { 507 configTimer.expires_after(std::chrono::seconds(1)); 508 // create a timer because normally multiple properties change 509 configTimer.async_wait([&](const boost::system::error_code& ec) { 510 if (ec == boost::asio::error::operation_aborted) 511 { 512 return; // we're being canceled 513 } 514 // config timer error 515 if (ec) 516 { 517 lg2::error("timer error"); 518 return; 519 } 520 createSensors(io, objectServer, sensors, systemBus); 521 if (sensors.empty()) 522 { 523 lg2::info("Configuration not detected"); 524 } 525 }); 526 }; 527 528 sdbusplus::bus::match_t configMatch( 529 static_cast<sdbusplus::bus_t&>(*systemBus), 530 "type='signal',member='PropertiesChanged'," 531 "path_namespace='" + 532 std::string(inventoryPath) + 533 "'," 534 "arg0namespace='" + 535 configInterface + "'", 536 eventHandler); 537 538 setupManufacturingModeMatch(*systemBus); 539 io.run(); 540 return 0; 541 } 542