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
SmbpbiSensor(std::shared_ptr<sdbusplus::asio::connection> & conn,boost::asio::io_context & io,const std::string & sensorName,const std::string & sensorConfiguration,const std::string & objType,sdbusplus::asio::object_server & objectServer,std::vector<thresholds::Threshold> && thresholdData,uint8_t busId,uint8_t addr,uint16_t offset,std::string & sensorUnits,std::string & valueType,size_t pollTime,double minVal,double maxVal,std::string & path)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
~SmbpbiSensor()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
init()118 void SmbpbiSensor::init()
119 {
120 read();
121 }
122
checkThresholds()123 void SmbpbiSensor::checkThresholds()
124 {
125 thresholds::checkThresholds(this);
126 }
127
convert2Temp(const uint8_t * raw)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
convert2Power(const uint8_t * raw)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
i2cReadDataBytesDouble(double & reading)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
i2cReadDataBytesUI64(uint64_t & reading)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
i2cReadDataBytes(uint8_t * reading,int length)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
readRawEEPROMData(double & data)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
readFloat64EEPROMData(double & data)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
waitReadCallback(const boost::system::error_code & ec)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
read()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
createSensorCallback(boost::system::error_code ec,const ManagedObjectType & resp,boost::asio::io_context & io,sdbusplus::asio::object_server & objectServer,std::shared_ptr<sdbusplus::asio::connection> & dbusConnection,boost::container::flat_map<std::string,std::unique_ptr<SmbpbiSensor>> & sensors)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
createSensors(boost::asio::io_context & io,sdbusplus::asio::object_server & objectServer,boost::container::flat_map<std::string,std::unique_ptr<SmbpbiSensor>> & sensors,std::shared_ptr<sdbusplus::asio::connection> & dbusConnection)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
main()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