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