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(std::shared_ptr<sdbusplus::asio::connection>& conn,
65                              boost::asio::io_context& io,
66                              const std::string& sensorName,
67                              const std::string& sensorConfiguration,
68                              sdbusplus::asio::object_server& objectServer,
69                              std::vector<thresholds::Threshold>&& thresholdData,
70                              uint8_t busId, uint8_t mcuAddress,
71                              uint8_t tempReg) :
72     Sensor(escapeName(sensorName), std::move(thresholdData),
73            sensorConfiguration, "MCUTempSensor", false, false,
74            mcuTempMaxReading, mcuTempMinReading, conn),
75     busId(busId), mcuAddress(mcuAddress), tempReg(tempReg),
76     objectServer(objectServer), waitTimer(io)
77 {
78     sensorInterface = objectServer.add_interface(
79         "/xyz/openbmc_project/sensors/temperature/" + name,
80         "xyz.openbmc_project.Sensor.Value");
81 
82     for (const auto& threshold : thresholds)
83     {
84         std::string interface = thresholds::getInterface(threshold.level);
85         thresholdInterfaces[static_cast<size_t>(threshold.level)] =
86             objectServer.add_interface(
87                 "/xyz/openbmc_project/sensors/temperature/" + name, interface);
88     }
89     association = objectServer.add_interface(
90         "/xyz/openbmc_project/sensors/temperature/" + name,
91         association::interface);
92 }
93 
~MCUTempSensor()94 MCUTempSensor::~MCUTempSensor()
95 {
96     waitTimer.cancel();
97     for (const auto& iface : thresholdInterfaces)
98     {
99         objectServer.remove_interface(iface);
100     }
101     objectServer.remove_interface(sensorInterface);
102     objectServer.remove_interface(association);
103 }
104 
init()105 void MCUTempSensor::init()
106 {
107     setInitialProperties(sensor_paths::unitDegreesC);
108     read();
109 }
110 
checkThresholds()111 void MCUTempSensor::checkThresholds()
112 {
113     thresholds::checkThresholds(this);
114 }
115 
getMCURegsInfoWord(uint8_t regs,int32_t * pu32data) const116 int MCUTempSensor::getMCURegsInfoWord(uint8_t regs, int32_t* pu32data) const
117 {
118     std::string i2cBus = "/dev/i2c-" + std::to_string(busId);
119 
120     // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
121     int fd = open(i2cBus.c_str(), O_RDWR);
122     if (fd < 0)
123     {
124         std::cerr << " unable to open i2c device" << i2cBus << "  err=" << fd
125                   << "\n";
126         return -1;
127     }
128 
129     // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
130     if (ioctl(fd, I2C_SLAVE_FORCE, mcuAddress) < 0)
131     {
132         std::cerr << " unable to set device address\n";
133         close(fd);
134         return -1;
135     }
136 
137     unsigned long funcs = 0;
138     // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
139     if (ioctl(fd, I2C_FUNCS, &funcs) < 0)
140     {
141         std::cerr << " not support I2C_FUNCS\n";
142         close(fd);
143         return -1;
144     }
145 
146     if ((funcs & I2C_FUNC_SMBUS_READ_WORD_DATA) == 0U)
147     {
148         std::cerr << " not support I2C_FUNC_SMBUS_READ_WORD_DATA\n";
149         close(fd);
150         return -1;
151     }
152 
153     *pu32data = i2c_smbus_read_word_data(fd, regs);
154     close(fd);
155 
156     if (*pu32data < 0)
157     {
158         std::cerr << " read word data failed at " << static_cast<int>(regs)
159                   << "\n";
160         return -1;
161     }
162 
163     return 0;
164 }
165 
read()166 void MCUTempSensor::read()
167 {
168     static constexpr size_t pollTime = 1; // in seconds
169 
170     waitTimer.expires_after(std::chrono::seconds(pollTime));
171     waitTimer.async_wait([this](const boost::system::error_code& ec) {
172         if (ec == boost::asio::error::operation_aborted)
173         {
174             return; // we're being cancelled
175         }
176         // read timer error
177         if (ec)
178         {
179             std::cerr << "timer error\n";
180             return;
181         }
182         int32_t temp = 0;
183         int ret = getMCURegsInfoWord(tempReg, &temp);
184         if (ret >= 0)
185         {
186             double v = static_cast<double>(temp) / 1000;
187             if constexpr (debug)
188             {
189                 std::cerr << "Value update to " << v << "raw reading "
190                           << static_cast<int>(temp) << "\n";
191             }
192             updateValue(v);
193         }
194         else
195         {
196             std::cerr << "Invalid read getMCURegsInfoWord\n";
197             incrementError();
198         }
199         read();
200     });
201 }
202 
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)203 void createSensors(
204     boost::asio::io_context& io, sdbusplus::asio::object_server& objectServer,
205     boost::container::flat_map<std::string, std::unique_ptr<MCUTempSensor>>&
206         sensors,
207     std::shared_ptr<sdbusplus::asio::connection>& dbusConnection)
208 {
209     if (!dbusConnection)
210     {
211         std::cerr << "Connection not created\n";
212         return;
213     }
214 
215     dbusConnection->async_method_call(
216         [&io, &objectServer, &dbusConnection, &sensors](
217             boost::system::error_code ec, const ManagedObjectType& resp) {
218         if (ec)
219         {
220             std::cerr << "Error contacting entity manager\n";
221             return;
222         }
223         for (const auto& [path, interfaces] : resp)
224         {
225             for (const auto& [intf, cfg] : interfaces)
226             {
227                 if (intf != configInterfaceName(sensorType))
228                 {
229                     continue;
230                 }
231                 std::string name = loadVariant<std::string>(cfg, "Name");
232 
233                 std::vector<thresholds::Threshold> sensorThresholds;
234                 if (!parseThresholdsFromConfig(interfaces, 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 = loadVariant<std::string>(cfg,
245                                                                    "Class");
246 
247                 if constexpr (debug)
248                 {
249                     std::cerr << "Configuration parsed for \n\t" << intf << "\n"
250                               << "with\n"
251                               << "\tName: " << name << "\n"
252                               << "\tBus: " << static_cast<int>(busId) << "\n"
253                               << "\tAddress: " << static_cast<int>(mcuAddress)
254                               << "\n"
255                               << "\tReg: " << static_cast<int>(tempReg) << "\n"
256                               << "\tClass: " << sensorClass << "\n";
257                 }
258 
259                 auto& sensor = sensors[name];
260 
261                 sensor = std::make_unique<MCUTempSensor>(
262                     dbusConnection, io, name, path, objectServer,
263                     std::move(sensorThresholds), busId, mcuAddress, tempReg);
264 
265                 sensor->init();
266             }
267         }
268     },
269         entityManagerName, "/xyz/openbmc_project/inventory",
270         "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
271 }
272 
main()273 int main()
274 {
275     boost::asio::io_context io;
276     auto systemBus = std::make_shared<sdbusplus::asio::connection>(io);
277     sdbusplus::asio::object_server objectServer(systemBus, true);
278     objectServer.add_manager("/xyz/openbmc_project/sensors");
279 
280     systemBus->request_name("xyz.openbmc_project.MCUTempSensor");
281 
282     boost::asio::post(
283         io, [&]() { createSensors(io, objectServer, sensors, systemBus); });
284 
285     boost::asio::steady_timer configTimer(io);
286 
287     std::function<void(sdbusplus::message_t&)> eventHandler =
288         [&](sdbusplus::message_t&) {
289         configTimer.expires_after(std::chrono::seconds(1));
290         // create a timer because normally multiple properties change
291         configTimer.async_wait([&](const boost::system::error_code& ec) {
292             if (ec == boost::asio::error::operation_aborted)
293             {
294                 return; // we're being canceled
295             }
296             // config timer error
297             if (ec)
298             {
299                 std::cerr << "timer error\n";
300                 return;
301             }
302             createSensors(io, objectServer, sensors, systemBus);
303             if (sensors.empty())
304             {
305                 std::cout << "Configuration not detected\n";
306             }
307         });
308     };
309 
310     std::vector<std::unique_ptr<sdbusplus::bus::match_t>> matches =
311         setupPropertiesChangedMatches(
312             *systemBus, std::to_array<const char*>({sensorType}), eventHandler);
313     setupManufacturingModeMatch(*systemBus);
314     io.run();
315     return 0;
316 }
317