xref: /openbmc/dbus-sensors/src/smbpbi/SmbpbiSensor.cpp (revision ced35e14360cb13b92ca0ac394037acbdbbcacc0)
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 <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 
39 extern "C"
40 {
41 #include <linux/i2c-dev.h>
42 #include <sys/ioctl.h>
43 }
44 
45 constexpr const bool debug = false;
46 
47 constexpr const char* configInterface =
48     "xyz.openbmc_project.Configuration.SmbpbiVirtualEeprom";
49 constexpr const char* sensorRootPath = "/xyz/openbmc_project/sensors/";
50 constexpr const char* objectType = "SmbpbiVirtualEeprom";
51 
52 boost::container::flat_map<std::string, std::unique_ptr<SmbpbiSensor>> sensors;
53 
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)54 SmbpbiSensor::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 
~SmbpbiSensor()107 SmbpbiSensor::~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 
init()119 void SmbpbiSensor::init()
120 {
121     read();
122 }
123 
checkThresholds()124 void SmbpbiSensor::checkThresholds()
125 {
126     thresholds::checkThresholds(this);
127 }
128 
convert2Temp(const uint8_t * raw)129 double 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 
convert2Power(const uint8_t * raw)152 double 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 
i2cReadDataBytesDouble(double & reading)163 int 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 
i2cReadDataBytesUI64(uint64_t & reading)193 int 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
i2cReadDataBytes(uint8_t * reading,int length)215 int 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 
readRawEEPROMData(double & data)279 int 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 
readFloat64EEPROMData(double & data)320 int 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 
waitReadCallback(const boost::system::error_code & ec)332 void 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 
read()379 void 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 
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)389 static 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 
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)475 void 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 
main()497 int 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 
511     std::function<void(sdbusplus::message_t&)> eventHandler =
512         [&](sdbusplus::message_t&) {
513             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 
534     sdbusplus::bus::match_t configMatch(
535         static_cast<sdbusplus::bus_t&>(*systemBus),
536         "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 }
548