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