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