xref: /openbmc/dbus-sensors/src/mcu/MCUTempSensor.cpp (revision d7be555ee0d885418e9a862b16565a0474c68d14)
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 <sdbusplus/asio/connection.hpp>
35 #include <sdbusplus/asio/object_server.hpp>
36 #include <sdbusplus/bus/match.hpp>
37 #include <sdbusplus/message.hpp>
38 
39 #include <array>
40 #include <chrono>
41 #include <cstddef>
42 #include <cstdint>
43 #include <functional>
44 #include <iostream>
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         std::cerr << " unable to open i2c device" << i2cBus << "  err=" << fd
124                   << "\n";
125         return -1;
126     }
127 
128     // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
129     if (ioctl(fd, I2C_SLAVE_FORCE, mcuAddress) < 0)
130     {
131         std::cerr << " unable to set device address\n";
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         std::cerr << " not support I2C_FUNCS\n";
141         close(fd);
142         return -1;
143     }
144 
145     if ((funcs & I2C_FUNC_SMBUS_READ_WORD_DATA) == 0U)
146     {
147         std::cerr << " not support I2C_FUNC_SMBUS_READ_WORD_DATA\n";
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         std::cerr << " read word data failed at " << static_cast<int>(regs)
158                   << "\n";
159         return -1;
160     }
161 
162     return 0;
163 }
164 
read()165 void MCUTempSensor::read()
166 {
167     static constexpr size_t pollTime = 1; // in seconds
168 
169     waitTimer.expires_after(std::chrono::seconds(pollTime));
170     waitTimer.async_wait([this](const boost::system::error_code& ec) {
171         if (ec == boost::asio::error::operation_aborted)
172         {
173             return; // we're being cancelled
174         }
175         // read timer error
176         if (ec)
177         {
178             std::cerr << "timer error\n";
179             return;
180         }
181         int32_t temp = 0;
182         int ret = getMCURegsInfoWord(tempReg, &temp);
183         if (ret >= 0)
184         {
185             double v = static_cast<double>(temp) / 1000;
186             if constexpr (debug)
187             {
188                 std::cerr << "Value update to " << v << "raw reading "
189                           << static_cast<int>(temp) << "\n";
190             }
191             updateValue(v);
192         }
193         else
194         {
195             std::cerr << "Invalid read getMCURegsInfoWord\n";
196             incrementError();
197         }
198         read();
199     });
200 }
201 
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)202 void createSensors(
203     boost::asio::io_context& io, sdbusplus::asio::object_server& objectServer,
204     boost::container::flat_map<std::string, std::unique_ptr<MCUTempSensor>>&
205         sensors,
206     std::shared_ptr<sdbusplus::asio::connection>& dbusConnection)
207 {
208     if (!dbusConnection)
209     {
210         std::cerr << "Connection not created\n";
211         return;
212     }
213 
214     dbusConnection->async_method_call(
215         [&io, &objectServer, &dbusConnection, &sensors](
216             boost::system::error_code ec, const ManagedObjectType& resp) {
217             if (ec)
218             {
219                 std::cerr << "Error contacting entity manager\n";
220                 return;
221             }
222             for (const auto& [path, interfaces] : resp)
223             {
224                 for (const auto& [intf, cfg] : interfaces)
225                 {
226                     if (intf != configInterfaceName(sensorType))
227                     {
228                         continue;
229                     }
230                     std::string name = loadVariant<std::string>(cfg, "Name");
231 
232                     std::vector<thresholds::Threshold> sensorThresholds;
233                     if (!parseThresholdsFromConfig(interfaces,
234                                                    sensorThresholds))
235                     {
236                         std::cerr << "error populating thresholds for " << name
237                                   << "\n";
238                     }
239 
240                     uint8_t busId = loadVariant<uint8_t>(cfg, "Bus");
241                     uint8_t mcuAddress = loadVariant<uint8_t>(cfg, "Address");
242                     uint8_t tempReg = loadVariant<uint8_t>(cfg, "Reg");
243 
244                     std::string sensorClass =
245                         loadVariant<std::string>(cfg, "Class");
246 
247                     if constexpr (debug)
248                     {
249                         std::cerr
250                             << "Configuration parsed for \n\t" << intf << "\n"
251                             << "with\n"
252                             << "\tName: " << name << "\n"
253                             << "\tBus: " << static_cast<int>(busId) << "\n"
254                             << "\tAddress: " << static_cast<int>(mcuAddress)
255                             << "\n"
256                             << "\tReg: " << static_cast<int>(tempReg) << "\n"
257                             << "\tClass: " << sensorClass << "\n";
258                     }
259 
260                     auto& sensor = sensors[name];
261 
262                     sensor = std::make_unique<MCUTempSensor>(
263                         dbusConnection, io, name, path, objectServer,
264                         std::move(sensorThresholds), busId, mcuAddress,
265                         tempReg);
266 
267                     sensor->init();
268                 }
269             }
270         },
271         entityManagerName, "/xyz/openbmc_project/inventory",
272         "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
273 }
274 
main()275 int main()
276 {
277     boost::asio::io_context io;
278     auto systemBus = std::make_shared<sdbusplus::asio::connection>(io);
279     sdbusplus::asio::object_server objectServer(systemBus, true);
280     objectServer.add_manager("/xyz/openbmc_project/sensors");
281 
282     systemBus->request_name("xyz.openbmc_project.MCUTempSensor");
283 
284     boost::asio::post(io, [&]() {
285         createSensors(io, objectServer, sensors, systemBus);
286     });
287 
288     boost::asio::steady_timer configTimer(io);
289 
290     std::function<void(sdbusplus::message_t&)> eventHandler =
291         [&](sdbusplus::message_t&) {
292             configTimer.expires_after(std::chrono::seconds(1));
293             // create a timer because normally multiple properties change
294             configTimer.async_wait([&](const boost::system::error_code& ec) {
295                 if (ec == boost::asio::error::operation_aborted)
296                 {
297                     return; // we're being canceled
298                 }
299                 // config timer error
300                 if (ec)
301                 {
302                     std::cerr << "timer error\n";
303                     return;
304                 }
305                 createSensors(io, objectServer, sensors, systemBus);
306                 if (sensors.empty())
307                 {
308                     std::cout << "Configuration not detected\n";
309                 }
310             });
311         };
312 
313     std::vector<std::unique_ptr<sdbusplus::bus::match_t>> matches =
314         setupPropertiesChangedMatches(
315             *systemBus, std::to_array<const char*>({sensorType}), eventHandler);
316     setupManufacturingModeMatch(*systemBus);
317     io.run();
318     return 0;
319 }
320