xref: /openbmc/dbus-sensors/src/mcu/MCUTempSensor.cpp (revision f2a2baae26ed85ef2096046f8457e5e0fbb1fb3f)
1 /*
2 // Copyright (c) 2019 Intel Corporation
3 //
4 // Licensed under the Apache License, Version 2.0 (the "License");
5 // you may not use this file except in compliance with the License.
6 // You may obtain a copy of the License at
7 //
8 //      http://www.apache.org/licenses/LICENSE-2.0
9 //
10 // Unless required by applicable law or agreed to in writing, software
11 // distributed under the License is distributed on an "AS IS" BASIS,
12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 // See the License for the specific language governing permissions and
14 // limitations under the License.
15 */
16 
17 #include "MCUTempSensor.hpp"
18 
19 #include "SensorPaths.hpp"
20 #include "Thresholds.hpp"
21 #include "Utils.hpp"
22 #include "sensor.hpp"
23 
24 #include <fcntl.h>
25 #include <linux/i2c.h>
26 #include <sys/ioctl.h>
27 #include <unistd.h>
28 
29 #include <boost/asio/error.hpp>
30 #include <boost/asio/io_context.hpp>
31 #include <boost/asio/post.hpp>
32 #include <boost/asio/steady_timer.hpp>
33 #include <boost/container/flat_map.hpp>
34 #include <phosphor-logging/lg2.hpp>
35 #include <sdbusplus/asio/connection.hpp>
36 #include <sdbusplus/asio/object_server.hpp>
37 #include <sdbusplus/bus/match.hpp>
38 #include <sdbusplus/message.hpp>
39 
40 #include <array>
41 #include <chrono>
42 #include <cstddef>
43 #include <cstdint>
44 #include <functional>
45 #include <memory>
46 #include <string>
47 #include <utility>
48 #include <vector>
49 
50 extern "C"
51 {
52 #include <i2c/smbus.h>
53 #include <linux/i2c-dev.h>
54 }
55 
56 constexpr const bool debug = false;
57 
58 constexpr const char* sensorType = "MCUTempSensor";
59 static constexpr double mcuTempMaxReading = 0xFF;
60 static constexpr double mcuTempMinReading = 0;
61 
62 boost::container::flat_map<std::string, std::unique_ptr<MCUTempSensor>> sensors;
63 
MCUTempSensor(std::shared_ptr<sdbusplus::asio::connection> & conn,boost::asio::io_context & io,const std::string & sensorName,const std::string & sensorConfiguration,sdbusplus::asio::object_server & objectServer,std::vector<thresholds::Threshold> && thresholdData,uint8_t busId,uint8_t mcuAddress,uint8_t tempReg)64 MCUTempSensor::MCUTempSensor(
65     std::shared_ptr<sdbusplus::asio::connection>& conn,
66     boost::asio::io_context& io, const std::string& sensorName,
67     const std::string& sensorConfiguration,
68     sdbusplus::asio::object_server& objectServer,
69     std::vector<thresholds::Threshold>&& thresholdData, uint8_t busId,
70     uint8_t mcuAddress, uint8_t tempReg) :
71     Sensor(escapeName(sensorName), std::move(thresholdData),
72            sensorConfiguration, "MCUTempSensor", false, false,
73            mcuTempMaxReading, mcuTempMinReading, conn),
74     busId(busId), mcuAddress(mcuAddress), tempReg(tempReg),
75     objectServer(objectServer), waitTimer(io)
76 {
77     sensorInterface = objectServer.add_interface(
78         "/xyz/openbmc_project/sensors/temperature/" + name,
79         "xyz.openbmc_project.Sensor.Value");
80 
81     for (const auto& threshold : thresholds)
82     {
83         std::string interface = thresholds::getInterface(threshold.level);
84         thresholdInterfaces[static_cast<size_t>(threshold.level)] =
85             objectServer.add_interface(
86                 "/xyz/openbmc_project/sensors/temperature/" + name, interface);
87     }
88     association = objectServer.add_interface(
89         "/xyz/openbmc_project/sensors/temperature/" + name,
90         association::interface);
91 }
92 
~MCUTempSensor()93 MCUTempSensor::~MCUTempSensor()
94 {
95     waitTimer.cancel();
96     for (const auto& iface : thresholdInterfaces)
97     {
98         objectServer.remove_interface(iface);
99     }
100     objectServer.remove_interface(sensorInterface);
101     objectServer.remove_interface(association);
102 }
103 
init()104 void MCUTempSensor::init()
105 {
106     setInitialProperties(sensor_paths::unitDegreesC);
107     read();
108 }
109 
checkThresholds()110 void MCUTempSensor::checkThresholds()
111 {
112     thresholds::checkThresholds(this);
113 }
114 
getMCURegsInfoWord(uint8_t regs,int32_t * pu32data) const115 int MCUTempSensor::getMCURegsInfoWord(uint8_t regs, int32_t* pu32data) const
116 {
117     std::string i2cBus = "/dev/i2c-" + std::to_string(busId);
118 
119     // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
120     int fd = open(i2cBus.c_str(), O_RDWR);
121     if (fd < 0)
122     {
123         lg2::error("unable to open i2c device '{BUS}' err = '{ERR}'", "BUS",
124                    i2cBus, "ERR", fd);
125         return -1;
126     }
127 
128     // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
129     if (ioctl(fd, I2C_SLAVE_FORCE, mcuAddress) < 0)
130     {
131         lg2::error("unable to set device address");
132         close(fd);
133         return -1;
134     }
135 
136     unsigned long funcs = 0;
137     // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
138     if (ioctl(fd, I2C_FUNCS, &funcs) < 0)
139     {
140         lg2::error("not support I2C_FUNCS");
141         close(fd);
142         return -1;
143     }
144 
145     if ((funcs & I2C_FUNC_SMBUS_READ_WORD_DATA) == 0U)
146     {
147         lg2::error("not support I2C_FUNC_SMBUS_READ_WORD_DATA");
148         close(fd);
149         return -1;
150     }
151 
152     *pu32data = i2c_smbus_read_word_data(fd, regs);
153     close(fd);
154 
155     if (*pu32data < 0)
156     {
157         lg2::error(" read word data failed at '{REGS}'", "REGS", regs);
158         return -1;
159     }
160 
161     return 0;
162 }
163 
read()164 void MCUTempSensor::read()
165 {
166     static constexpr size_t pollTime = 1; // in seconds
167 
168     waitTimer.expires_after(std::chrono::seconds(pollTime));
169     waitTimer.async_wait([this](const boost::system::error_code& ec) {
170         if (ec == boost::asio::error::operation_aborted)
171         {
172             return; // we're being cancelled
173         }
174         // read timer error
175         if (ec)
176         {
177             lg2::error("timer error");
178             return;
179         }
180         int32_t temp = 0;
181         int ret = getMCURegsInfoWord(tempReg, &temp);
182         if (ret >= 0)
183         {
184             double v = static_cast<double>(temp) / 1000;
185             if constexpr (debug)
186             {
187                 lg2::error("Value update to '{VALUE}' raw reading '{RAW}'",
188                            "VALUE", v, "RAW", temp);
189             }
190             updateValue(v);
191         }
192         else
193         {
194             lg2::error("Invalid read getMCURegsInfoWord");
195             incrementError();
196         }
197         read();
198     });
199 }
200 
createSensors(boost::asio::io_context & io,sdbusplus::asio::object_server & objectServer,boost::container::flat_map<std::string,std::unique_ptr<MCUTempSensor>> & sensors,std::shared_ptr<sdbusplus::asio::connection> & dbusConnection)201 void createSensors(
202     boost::asio::io_context& io, sdbusplus::asio::object_server& objectServer,
203     boost::container::flat_map<std::string, std::unique_ptr<MCUTempSensor>>&
204         sensors,
205     std::shared_ptr<sdbusplus::asio::connection>& dbusConnection)
206 {
207     if (!dbusConnection)
208     {
209         lg2::error("Connection not created");
210         return;
211     }
212 
213     dbusConnection->async_method_call(
214         [&io, &objectServer, &dbusConnection, &sensors](
215             boost::system::error_code ec, const ManagedObjectType& resp) {
216             if (ec)
217             {
218                 lg2::error("Error contacting entity manager");
219                 return;
220             }
221             for (const auto& [path, interfaces] : resp)
222             {
223                 for (const auto& [intf, cfg] : interfaces)
224                 {
225                     if (intf != configInterfaceName(sensorType))
226                     {
227                         continue;
228                     }
229                     std::string name = loadVariant<std::string>(cfg, "Name");
230 
231                     std::vector<thresholds::Threshold> sensorThresholds;
232                     if (!parseThresholdsFromConfig(interfaces,
233                                                    sensorThresholds))
234                     {
235                         lg2::error("error populating thresholds for '{NAME}'",
236                                    "NAME", name);
237                     }
238 
239                     uint8_t busId = loadVariant<uint8_t>(cfg, "Bus");
240                     uint8_t mcuAddress = loadVariant<uint8_t>(cfg, "Address");
241                     uint8_t tempReg = loadVariant<uint8_t>(cfg, "Reg");
242 
243                     std::string sensorClass =
244                         loadVariant<std::string>(cfg, "Class");
245 
246                     if constexpr (debug)
247                     {
248                         lg2::error(
249                             "Configuration parsed for '{INTERFACE}' with Name: {NAME}, Bus: {BUS}, "
250                             "Address: {ADDRESS}, Reg: {REG}, Class: {CLASS}",
251                             "INTERFACE", intf, "NAME", name, "BUS", busId,
252                             "ADDRESS", mcuAddress, "REG", tempReg, "CLASS",
253                             sensorClass);
254                     }
255 
256                     auto& sensor = sensors[name];
257 
258                     sensor = std::make_unique<MCUTempSensor>(
259                         dbusConnection, io, name, path, objectServer,
260                         std::move(sensorThresholds), busId, mcuAddress,
261                         tempReg);
262 
263                     sensor->init();
264                 }
265             }
266         },
267         entityManagerName, "/xyz/openbmc_project/inventory",
268         "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
269 }
270 
main()271 int main()
272 {
273     boost::asio::io_context io;
274     auto systemBus = std::make_shared<sdbusplus::asio::connection>(io);
275     sdbusplus::asio::object_server objectServer(systemBus, true);
276     objectServer.add_manager("/xyz/openbmc_project/sensors");
277 
278     systemBus->request_name("xyz.openbmc_project.MCUTempSensor");
279 
280     boost::asio::post(io, [&]() {
281         createSensors(io, objectServer, sensors, systemBus);
282     });
283 
284     boost::asio::steady_timer configTimer(io);
285 
286     std::function<void(sdbusplus::message_t&)> eventHandler =
287         [&](sdbusplus::message_t&) {
288             configTimer.expires_after(std::chrono::seconds(1));
289             // create a timer because normally multiple properties change
290             configTimer.async_wait([&](const boost::system::error_code& ec) {
291                 if (ec == boost::asio::error::operation_aborted)
292                 {
293                     return; // we're being canceled
294                 }
295                 // config timer error
296                 if (ec)
297                 {
298                     lg2::error("timer error");
299                     return;
300                 }
301                 createSensors(io, objectServer, sensors, systemBus);
302                 if (sensors.empty())
303                 {
304                     lg2::info("Configuration not detected");
305                 }
306             });
307         };
308 
309     std::vector<std::unique_ptr<sdbusplus::bus::match_t>> matches =
310         setupPropertiesChangedMatches(
311             *systemBus, std::to_array<const char*>({sensorType}), eventHandler);
312     setupManufacturingModeMatch(*systemBus);
313     io.run();
314     return 0;
315 }
316