xref: /openbmc/dbus-sensors/src/smbpbi/SmbpbiSensor.cpp (revision 0d2f96f1e95a6a37f00a3c458c8562d6af92e4a4)
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     struct i2c_msg msg = {0, 0, 0, nullptr};
236     std::array<uint8_t, 8> cmd{};
237 
238     msg.addr = addr;
239     args.msgs = &msg;
240     args.nmsgs = 1;
241 
242     msg.flags = 0;
243     msg.buf = cmd.data();
244     // handle two bytes offset
245     if (offset > 255)
246     {
247         msg.len = 2;
248         msg.buf[0] = offset >> 8;
249         msg.buf[1] = offset & 0xFF;
250     }
251     else
252     {
253         msg.len = 1;
254         msg.buf[0] = offset & 0xFF;
255     }
256 
257     // write offset
258     // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
259     ret = ioctl(fd, I2C_RDWR, &args);
260     if (ret < 0)
261     {
262         return ret;
263     }
264 
265     msg.flags = I2C_M_RD;
266     msg.len = length;
267     msg.buf = reading;
268 
269     // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
270     ret = ioctl(fd, I2C_RDWR, &args);
271     if (ret < 0)
272     {
273         return ret;
274     }
275     return 0;
276 }
277 
readRawEEPROMData(double & data)278 int SmbpbiSensor::readRawEEPROMData(double& data)
279 {
280     uint64_t reading = 0;
281     int ret = i2cReadDataBytesUI64(reading);
282     if (ret < 0)
283     {
284         return ret;
285     }
286     // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
287     if (checkInvalidReading(reinterpret_cast<uint8_t*>(&reading),
288                             sizeof(reading)))
289     {
290         data = std::numeric_limits<double>::quiet_NaN();
291         return 0;
292     }
293     if (debug)
294     {
295         lg2::error("offset: {OFFSET} reading: {READING}", "OFFSET", offset,
296                    "READING", reading);
297     }
298     if (sensorType == "temperature")
299     {
300         // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
301         data = convert2Temp(reinterpret_cast<uint8_t*>(&reading));
302     }
303     else if (sensorType == "power")
304     {
305         // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
306         data = convert2Power(reinterpret_cast<uint8_t*>(&reading));
307     }
308     else if (sensorType == "energy")
309     {
310         data = reading / 1000.0; // mJ to J (double)
311     }
312     else
313     {
314         data = reading; // Voltage
315     }
316     return 0;
317 }
318 
readFloat64EEPROMData(double & data)319 int SmbpbiSensor::readFloat64EEPROMData(double& data)
320 {
321     double reading = 0;
322     int ret = i2cReadDataBytesDouble(reading);
323     if (ret < 0)
324     {
325         return ret;
326     }
327     data = reading;
328     return 0;
329 }
330 
waitReadCallback(const boost::system::error_code & ec)331 void SmbpbiSensor::waitReadCallback(const boost::system::error_code& ec)
332 {
333     if (ec == boost::asio::error::operation_aborted)
334     {
335         // we're being cancelled
336         return;
337     }
338     // read timer error
339     if (ec)
340     {
341         lg2::error("timer error");
342         return;
343     }
344     double temp = 0;
345 
346     int ret = 0;
347     // Sensor reading value types are sensor-specific. So, read
348     // and interpret sensor data based on it's value type.
349     if (valueType == "UINT64")
350     {
351         ret = readRawEEPROMData(temp);
352     }
353     else if (valueType == "FLOAT64")
354     {
355         ret = readFloat64EEPROMData(temp);
356     }
357     else
358     {
359         return;
360     }
361 
362     if (ret >= 0)
363     {
364         if constexpr (debug)
365         {
366             lg2::error("Value update to {TEMP}", "TEMP", temp);
367         }
368         updateValue(temp);
369     }
370     else
371     {
372         lg2::error("Invalid read getRegsInfo");
373         incrementError();
374     }
375     read();
376 }
377 
read()378 void SmbpbiSensor::read()
379 {
380     size_t pollTime = getPollRate(); // in seconds
381 
382     waitTimer.expires_after(std::chrono::seconds(pollTime));
383     waitTimer.async_wait([this](const boost::system::error_code& ec) {
384         this->waitReadCallback(ec);
385     });
386 }
387 
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)388 static void createSensorCallback(
389     boost::system::error_code ec, const ManagedObjectType& resp,
390     boost::asio::io_context& io, sdbusplus::asio::object_server& objectServer,
391     std::shared_ptr<sdbusplus::asio::connection>& dbusConnection,
392     boost::container::flat_map<std::string, std::unique_ptr<SmbpbiSensor>>&
393         sensors)
394 {
395     if (ec)
396     {
397         lg2::error("Error contacting entity manager");
398         return;
399     }
400     for (const auto& pathPair : resp)
401     {
402         for (const auto& entry : pathPair.second)
403         {
404             if (entry.first != configInterface)
405             {
406                 continue;
407             }
408             std::string name = loadVariant<std::string>(entry.second, "Name");
409 
410             std::vector<thresholds::Threshold> sensorThresholds;
411             if (!parseThresholdsFromConfig(pathPair.second, sensorThresholds))
412             {
413                 lg2::error("error populating thresholds for {NAME}", "NAME",
414                            name);
415             }
416 
417             uint8_t busId = loadVariant<uint8_t>(entry.second, "Bus");
418 
419             uint8_t addr = loadVariant<uint8_t>(entry.second, "Address");
420 
421             uint16_t off = loadVariant<uint16_t>(entry.second, "ReadOffset");
422 
423             std::string sensorUnits =
424                 loadVariant<std::string>(entry.second, "Units");
425 
426             std::string valueType =
427                 loadVariant<std::string>(entry.second, "ValueType");
428             if (valueType != "UINT64" && valueType != "FLOAT64")
429             {
430                 lg2::error("Invalid ValueType for sensor: {NAME}", "NAME",
431                            name);
432                 break;
433             }
434 
435             size_t rate = loadVariant<uint8_t>(entry.second, "PollRate");
436 
437             double minVal = loadVariant<double>(entry.second, "MinValue");
438 
439             double maxVal = loadVariant<double>(entry.second, "MaxValue");
440             if constexpr (debug)
441             {
442                 lg2::info("Configuration parsed for \n\t {CONF}\nwith\n"
443                           "\tName: {NAME}\n"
444                           "\tBus: {BUS}\n"
445                           "\tAddress:{ADDR}\n"
446                           "\tOffset: {OFF}\n"
447                           "\tType : {TYPE}\n"
448                           "\tValue Type : {VALUETYPE}\n"
449                           "\tPollrate: {RATE}\n"
450                           "\tMinValue: {MIN}\n"
451                           "\tMaxValue: {MAX}\n",
452                           "CONF", entry.first, "NAME", name, "BUS",
453                           static_cast<int>(busId), "ADDR",
454                           static_cast<int>(addr), "OFF", static_cast<int>(off),
455                           "UNITS", sensorUnits, "VALUETYPE", valueType, "RATE",
456                           rate, "MIN", minVal, "MAX", maxVal);
457             }
458 
459             auto& sensor = sensors[name];
460             sensor = nullptr;
461 
462             std::string path = "/dev/i2c-" + std::to_string(busId);
463 
464             sensor = std::make_unique<SmbpbiSensor>(
465                 dbusConnection, io, name, pathPair.first, objectType,
466                 objectServer, std::move(sensorThresholds), busId, addr, off,
467                 sensorUnits, valueType, rate, minVal, maxVal, path);
468 
469             sensor->init();
470         }
471     }
472 }
473 
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)474 void createSensors(
475     boost::asio::io_context& io, sdbusplus::asio::object_server& objectServer,
476     boost::container::flat_map<std::string, std::unique_ptr<SmbpbiSensor>>&
477         sensors,
478     std::shared_ptr<sdbusplus::asio::connection>& dbusConnection)
479 {
480     if (!dbusConnection)
481     {
482         lg2::error("Connection not created");
483         return;
484     }
485 
486     dbusConnection->async_method_call(
487         [&io, &objectServer, &dbusConnection, &sensors](
488             boost::system::error_code ec, const ManagedObjectType& resp) {
489             createSensorCallback(ec, resp, io, objectServer, dbusConnection,
490                                  sensors);
491         },
492         entityManagerName, "/xyz/openbmc_project/inventory",
493         "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
494 }
495 
main()496 int main()
497 {
498     boost::asio::io_context io;
499     auto systemBus = std::make_shared<sdbusplus::asio::connection>(io);
500     sdbusplus::asio::object_server objectServer(systemBus, true);
501     objectServer.add_manager("/xyz/openbmc_project/sensors");
502     systemBus->request_name("xyz.openbmc_project.SMBPBI");
503 
504     boost::asio::post(io, [&]() {
505         createSensors(io, objectServer, sensors, systemBus);
506     });
507 
508     boost::asio::steady_timer configTimer(io);
509 
510     std::function<void(sdbusplus::message_t&)> eventHandler =
511         [&](sdbusplus::message_t&) {
512             configTimer.expires_after(std::chrono::seconds(1));
513             // create a timer because normally multiple properties change
514             configTimer.async_wait([&](const boost::system::error_code& ec) {
515                 if (ec == boost::asio::error::operation_aborted)
516                 {
517                     return; // we're being canceled
518                 }
519                 // config timer error
520                 if (ec)
521                 {
522                     lg2::error("timer error");
523                     return;
524                 }
525                 createSensors(io, objectServer, sensors, systemBus);
526                 if (sensors.empty())
527                 {
528                     lg2::info("Configuration not detected");
529                 }
530             });
531         };
532 
533     sdbusplus::bus::match_t configMatch(
534         static_cast<sdbusplus::bus_t&>(*systemBus),
535         "type='signal',member='PropertiesChanged',"
536         "path_namespace='" +
537             std::string(inventoryPath) +
538             "',"
539             "arg0namespace='" +
540             configInterface + "'",
541         eventHandler);
542 
543     setupManufacturingModeMatch(*systemBus);
544     io.run();
545     return 0;
546 }
547