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