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