xref: /openbmc/dbus-sensors/src/smbpbi/SmbpbiSensor.cpp (revision ff4c54d905c0b5afd9b53d8042d526a62ec8af50)
1 /*
2  * SPDX-FileCopyrightText: Copyright OpenBMC Authors
3  * 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>
22 #include <sdbusplus/bus.hpp>
23 #include <sdbusplus/bus/match.hpp>
24 #include <sdbusplus/message.hpp>
25 
26 #include <array>
27 #include <chrono>
28 #include <cstdint>
29 #include <cstring>
30 #include <functional>
31 #include <limits>
32 #include <memory>
33 #include <string>
34 #include <utility>
35 #include <vector>
36 
37 extern "C"
38 {
39 #include <linux/i2c-dev.h>
40 #include <sys/ioctl.h>
41 }
42 
43 constexpr const char* configInterface =
44     "xyz.openbmc_project.Configuration.SmbpbiVirtualEeprom";
45 constexpr const char* sensorRootPath = "/xyz/openbmc_project/sensors/";
46 constexpr const char* objectType = "SmbpbiVirtualEeprom";
47 
48 boost::container::flat_map<std::string, std::unique_ptr<SmbpbiSensor>> sensors;
49 
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,const PowerState & powerState)50 SmbpbiSensor::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, const PowerState& powerState) :
59     Sensor(escapeName(sensorName), std::move(thresholdData),
60            sensorConfiguration, objType, false, false, maxVal, minVal, conn,
61            powerState),
62     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 
~SmbpbiSensor()104 SmbpbiSensor::~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 
init()116 void SmbpbiSensor::init()
117 {
118     read();
119 }
120 
checkThresholds()121 void SmbpbiSensor::checkThresholds()
122 {
123     thresholds::checkThresholds(this);
124 }
125 
convert2Temp(const uint8_t * raw)126 double 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 
convert2Power(const uint8_t * raw)149 double 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 
i2cReadDataBytesDouble(double & reading)160 int 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 
i2cReadDataBytesUI64(uint64_t & reading)190 int 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
i2cReadDataBytes(uint8_t * reading,int length)212 int 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};
233     std::array<struct i2c_msg, 2> msgs = {
234         {{0, 0, 0, nullptr}, {0, 0, 0, nullptr}}};
235     std::array<uint8_t, 8> cmd{};
236 
237     args.msgs = msgs.data();
238     args.nmsgs = msgs.size();
239 
240     msgs[0].addr = addr;
241     msgs[0].flags = 0;
242     msgs[0].buf = cmd.data();
243     // handle two bytes offset
244     if (offset > 255)
245     {
246         msgs[0].len = 2;
247         msgs[0].buf[0] = offset >> 8;
248         msgs[0].buf[1] = offset & 0xFF;
249     }
250     else
251     {
252         msgs[0].len = 1;
253         msgs[0].buf[0] = offset & 0xFF;
254     }
255 
256     msgs[1].addr = addr;
257     msgs[1].flags = I2C_M_RD;
258     msgs[1].len = length;
259     msgs[1].buf = reading;
260 
261     // write offset
262     // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
263     ret = ioctl(fd, I2C_RDWR, &args);
264     if (ret < 0)
265     {
266         return ret;
267     }
268     return 0;
269 }
270 
readRawEEPROMData(double & data)271 int 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     }
286     lg2::debug("offset: {OFFSET} reading: {READING}", "OFFSET", offset,
287                "READING", reading);
288     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 
readFloat64EEPROMData(double & data)309 int 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 
waitReadCallback(const boost::system::error_code & ec)321 void 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     {
354         lg2::debug("Value update to {TEMP}", "TEMP", temp);
355         updateValue(temp);
356     }
357     else
358     {
359         lg2::error("Invalid read getRegsInfo");
360         incrementError();
361     }
362     read();
363 }
364 
read()365 void 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 
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)375 static 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 
410             PowerState pwrState = getPowerState(entry.second);
411 
412             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");
429             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);
445 
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,
454                 sensorUnits, valueType, rate, minVal, maxVal, path, pwrState);
455 
456             sensor->init();
457         }
458     }
459 }
460 
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)461 void 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 
main()483 int 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 
497     std::function<void(sdbusplus::message_t&)> eventHandler =
498         [&](sdbusplus::message_t&) {
499             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 
520     sdbusplus::bus::match_t configMatch(
521         static_cast<sdbusplus::bus_t&>(*systemBus),
522         "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 }
534