blob: d04deae99b90a3cc5e8e51def8926fa2ea2a7db9 [file] [log] [blame]
Aushim Nagarkatti021261c2024-12-12 10:12:16 -08001/*
Ed Tanousb5e823f2025-10-09 20:28:42 -04002 * SPDX-FileCopyrightText: Copyright OpenBMC Authors
Aushim Nagarkatti021261c2024-12-12 10:12:16 -08003 * SPDX-License-Identifier: Apache-2.0
4 */
5
6#include "SmbpbiSensor.hpp"
7
8#include "SensorPaths.hpp"
9#include "Thresholds.hpp"
10#include "Utils.hpp"
11#include "sensor.hpp"
12
13#include <linux/i2c.h>
14
15#include <boost/asio/error.hpp>
16#include <boost/asio/io_context.hpp>
17#include <boost/asio/post.hpp>
18#include <boost/container/flat_map.hpp>
19#include <phosphor-logging/lg2.hpp>
20#include <sdbusplus/asio/connection.hpp>
21#include <sdbusplus/asio/object_server.hpp>
Patrick Williamsced35e12025-05-30 02:34:57 -040022#include <sdbusplus/bus.hpp>
Aushim Nagarkatti021261c2024-12-12 10:12:16 -080023#include <sdbusplus/bus/match.hpp>
24#include <sdbusplus/message.hpp>
25
26#include <array>
27#include <chrono>
Aushim Nagarkatti021261c2024-12-12 10:12:16 -080028#include <cstdint>
29#include <cstring>
30#include <functional>
31#include <limits>
32#include <memory>
33#include <string>
34#include <utility>
35#include <vector>
36
37extern "C"
38{
39#include <linux/i2c-dev.h>
40#include <sys/ioctl.h>
41}
42
Aushim Nagarkatti021261c2024-12-12 10:12:16 -080043constexpr const char* configInterface =
44 "xyz.openbmc_project.Configuration.SmbpbiVirtualEeprom";
45constexpr const char* sensorRootPath = "/xyz/openbmc_project/sensors/";
46constexpr const char* objectType = "SmbpbiVirtualEeprom";
47
48boost::container::flat_map<std::string, std::unique_ptr<SmbpbiSensor>> sensors;
49
50SmbpbiSensor::SmbpbiSensor(
51 std::shared_ptr<sdbusplus::asio::connection>& conn,
52 boost::asio::io_context& io, const std::string& sensorName,
53 const std::string& sensorConfiguration, const std::string& objType,
54 sdbusplus::asio::object_server& objectServer,
55 std::vector<thresholds::Threshold>&& thresholdData, uint8_t busId,
56 uint8_t addr, uint16_t offset, std::string& sensorUnits,
57 std::string& valueType, size_t pollTime, double minVal, double maxVal,
Potin Laiff4c54d2025-06-08 11:05:34 +080058 std::string& path, const PowerState& powerState) :
Aushim Nagarkatti021261c2024-12-12 10:12:16 -080059 Sensor(escapeName(sensorName), std::move(thresholdData),
Potin Laiff4c54d2025-06-08 11:05:34 +080060 sensorConfiguration, objType, false, false, maxVal, minVal, conn,
61 powerState),
Aushim Nagarkatti021261c2024-12-12 10:12:16 -080062 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
104SmbpbiSensor::~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
116void SmbpbiSensor::init()
117{
118 read();
119}
120
121void SmbpbiSensor::checkThresholds()
122{
123 thresholds::checkThresholds(this);
124}
125
126double 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
149double 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
160int 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
190int 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
212int 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};
Potin Lai0662e602025-06-08 01:19:40 +0800233 std::array<struct i2c_msg, 2> msgs = {
234 {{0, 0, 0, nullptr}, {0, 0, 0, nullptr}}};
Aushim Nagarkatti021261c2024-12-12 10:12:16 -0800235 std::array<uint8_t, 8> cmd{};
236
Potin Lai0662e602025-06-08 01:19:40 +0800237 args.msgs = msgs.data();
238 args.nmsgs = msgs.size();
Aushim Nagarkatti021261c2024-12-12 10:12:16 -0800239
Potin Lai0662e602025-06-08 01:19:40 +0800240 msgs[0].addr = addr;
241 msgs[0].flags = 0;
242 msgs[0].buf = cmd.data();
Aushim Nagarkatti021261c2024-12-12 10:12:16 -0800243 // handle two bytes offset
244 if (offset > 255)
245 {
Potin Lai0662e602025-06-08 01:19:40 +0800246 msgs[0].len = 2;
247 msgs[0].buf[0] = offset >> 8;
248 msgs[0].buf[1] = offset & 0xFF;
Aushim Nagarkatti021261c2024-12-12 10:12:16 -0800249 }
250 else
251 {
Potin Lai0662e602025-06-08 01:19:40 +0800252 msgs[0].len = 1;
253 msgs[0].buf[0] = offset & 0xFF;
Aushim Nagarkatti021261c2024-12-12 10:12:16 -0800254 }
255
Potin Lai0662e602025-06-08 01:19:40 +0800256 msgs[1].addr = addr;
257 msgs[1].flags = I2C_M_RD;
258 msgs[1].len = length;
259 msgs[1].buf = reading;
260
Aushim Nagarkatti021261c2024-12-12 10:12:16 -0800261 // write offset
262 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
263 ret = ioctl(fd, I2C_RDWR, &args);
264 if (ret < 0)
265 {
266 return ret;
267 }
Aushim Nagarkatti021261c2024-12-12 10:12:16 -0800268 return 0;
269}
270
271int 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 }
Alexander Hansen89be6142025-09-18 15:36:16 +0200286 lg2::debug("offset: {OFFSET} reading: {READING}", "OFFSET", offset,
287 "READING", reading);
Aushim Nagarkatti021261c2024-12-12 10:12:16 -0800288 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
309int 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
321void 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 {
Alexander Hansen89be6142025-09-18 15:36:16 +0200354 lg2::debug("Value update to {TEMP}", "TEMP", temp);
Aushim Nagarkatti021261c2024-12-12 10:12:16 -0800355 updateValue(temp);
356 }
357 else
358 {
359 lg2::error("Invalid read getRegsInfo");
360 incrementError();
361 }
362 read();
363}
364
365void 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
375static 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
Potin Laiff4c54d2025-06-08 11:05:34 +0800410 PowerState pwrState = getPowerState(entry.second);
411
Aushim Nagarkatti021261c2024-12-12 10:12:16 -0800412 std::string sensorUnits =
413 loadVariant<std::string>(entry.second, "Units");
414
415 std::string valueType =
416 loadVariant<std::string>(entry.second, "ValueType");
417 if (valueType != "UINT64" && valueType != "FLOAT64")
418 {
419 lg2::error("Invalid ValueType for sensor: {NAME}", "NAME",
420 name);
421 break;
422 }
423
424 size_t rate = loadVariant<uint8_t>(entry.second, "PollRate");
425
426 double minVal = loadVariant<double>(entry.second, "MinValue");
427
428 double maxVal = loadVariant<double>(entry.second, "MaxValue");
Potin Laiff4c54d2025-06-08 11:05:34 +0800429 lg2::debug("Configuration parsed for \n\t {CONF}\nwith\n"
430 "\tName: {NAME}\n"
431 "\tBus: {BUS}\n"
432 "\tAddress:{ADDR}\n"
433 "\tOffset: {OFF}\n"
434 "\tType : {TYPE}\n"
435 "\tValue Type : {VALUETYPE}\n"
436 "\tPollrate: {RATE}\n"
437 "\tMinValue: {MIN}\n"
438 "\tMaxValue: {MAX}\n"
439 "\tPowerState: {PWRSTATE}\n",
440 "CONF", entry.first, "NAME", name, "BUS",
441 static_cast<int>(busId), "ADDR", static_cast<int>(addr),
442 "OFF", static_cast<int>(off), "UNITS", sensorUnits,
443 "VALUETYPE", valueType, "RATE", rate, "MIN", minVal,
444 "MAX", maxVal, "PWRSTATE", pwrState);
Aushim Nagarkatti021261c2024-12-12 10:12:16 -0800445
446 auto& sensor = sensors[name];
447 sensor = nullptr;
448
449 std::string path = "/dev/i2c-" + std::to_string(busId);
450
451 sensor = std::make_unique<SmbpbiSensor>(
452 dbusConnection, io, name, pathPair.first, objectType,
453 objectServer, std::move(sensorThresholds), busId, addr, off,
Potin Laiff4c54d2025-06-08 11:05:34 +0800454 sensorUnits, valueType, rate, minVal, maxVal, path, pwrState);
Aushim Nagarkatti021261c2024-12-12 10:12:16 -0800455
456 sensor->init();
457 }
458 }
459}
460
461void createSensors(
462 boost::asio::io_context& io, sdbusplus::asio::object_server& objectServer,
463 boost::container::flat_map<std::string, std::unique_ptr<SmbpbiSensor>>&
464 sensors,
465 std::shared_ptr<sdbusplus::asio::connection>& dbusConnection)
466{
467 if (!dbusConnection)
468 {
469 lg2::error("Connection not created");
470 return;
471 }
472
473 dbusConnection->async_method_call(
474 [&io, &objectServer, &dbusConnection, &sensors](
475 boost::system::error_code ec, const ManagedObjectType& resp) {
476 createSensorCallback(ec, resp, io, objectServer, dbusConnection,
477 sensors);
478 },
479 entityManagerName, "/xyz/openbmc_project/inventory",
480 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
481}
482
483int main()
484{
485 boost::asio::io_context io;
486 auto systemBus = std::make_shared<sdbusplus::asio::connection>(io);
487 sdbusplus::asio::object_server objectServer(systemBus, true);
488 objectServer.add_manager("/xyz/openbmc_project/sensors");
489 systemBus->request_name("xyz.openbmc_project.SMBPBI");
490
491 boost::asio::post(io, [&]() {
492 createSensors(io, objectServer, sensors, systemBus);
493 });
494
495 boost::asio::steady_timer configTimer(io);
496
Patrick Williamsced35e12025-05-30 02:34:57 -0400497 std::function<void(sdbusplus::message_t&)> eventHandler =
498 [&](sdbusplus::message_t&) {
Aushim Nagarkatti021261c2024-12-12 10:12:16 -0800499 configTimer.expires_after(std::chrono::seconds(1));
500 // create a timer because normally multiple properties change
501 configTimer.async_wait([&](const boost::system::error_code& ec) {
502 if (ec == boost::asio::error::operation_aborted)
503 {
504 return; // we're being canceled
505 }
506 // config timer error
507 if (ec)
508 {
509 lg2::error("timer error");
510 return;
511 }
512 createSensors(io, objectServer, sensors, systemBus);
513 if (sensors.empty())
514 {
515 lg2::info("Configuration not detected");
516 }
517 });
518 };
519
Patrick Williamsced35e12025-05-30 02:34:57 -0400520 sdbusplus::bus::match_t configMatch(
521 static_cast<sdbusplus::bus_t&>(*systemBus),
Aushim Nagarkatti021261c2024-12-12 10:12:16 -0800522 "type='signal',member='PropertiesChanged',"
523 "path_namespace='" +
524 std::string(inventoryPath) +
525 "',"
526 "arg0namespace='" +
527 configInterface + "'",
528 eventHandler);
529
530 setupManufacturingModeMatch(*systemBus);
531 io.run();
532 return 0;
533}