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