blob: 2870d337e17bc585d1e9a8eb91d89f1b515501c3 [file] [log] [blame]
James Feist6ef20402019-01-07 16:45:08 -08001/*
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 "IpmbSensor.hpp"
18
19#include "Utils.hpp"
20#include "VariantVisitors.hpp"
21
22#include <math.h>
23
24#include <boost/algorithm/string.hpp>
25#include <boost/algorithm/string/predicate.hpp>
26#include <boost/algorithm/string/replace.hpp>
27#include <chrono>
28#include <iostream>
29#include <limits>
30#include <numeric>
31#include <sdbusplus/asio/connection.hpp>
32#include <sdbusplus/asio/object_server.hpp>
33#include <vector>
34
35constexpr const bool debug = false;
36
37constexpr const char* configInterface =
38 "xyz.openbmc_project.Configuration.IpmbSensor";
39static constexpr double ipmbMaxReading = 0xFF;
40static constexpr double ipmbMinReading = 0;
41
42static constexpr uint8_t meAddress = 1;
43static constexpr uint8_t lun = 0;
44
45using IpmbMethodType =
46 std::tuple<int, uint8_t, uint8_t, uint8_t, uint8_t, std::vector<uint8_t>>;
47
James Feistf7e2c5d2019-02-13 17:27:51 -080048boost::container::flat_map<std::string, std::unique_ptr<IpmbSensor>> sensors;
49
James Feist0d4f2bd2019-03-05 13:15:40 -080050std::unique_ptr<boost::asio::deadline_timer> initCmdTimer;
51
James Feist6ef20402019-01-07 16:45:08 -080052IpmbSensor::IpmbSensor(std::shared_ptr<sdbusplus::asio::connection>& conn,
53 boost::asio::io_service& io,
54 const std::string& sensorName,
55 const std::string& sensorConfiguration,
56 sdbusplus::asio::object_server& objectServer,
57 std::vector<thresholds::Threshold>&& thresholdData,
58 uint8_t deviceAddress) :
59 Sensor(boost::replace_all_copy(sensorName, " ", "_"),
60 "" /* todo: remove arg from base*/, std::move(thresholdData),
61 sensorConfiguration, "xyz.openbmc_project.Configuration.ExitAirTemp",
62 ipmbMaxReading, ipmbMinReading),
63 objectServer(objectServer), dbusConnection(conn), waitTimer(io),
64 deviceAddress(deviceAddress)
65{
66 sensorInterface = objectServer.add_interface(
67 "/xyz/openbmc_project/sensors/temperature/" + name,
68 "xyz.openbmc_project.Sensor.Value");
69
70 if (thresholds::hasWarningInterface(thresholds))
71 {
72 thresholdInterfaceWarning = objectServer.add_interface(
73 "/xyz/openbmc_project/sensors/temperature/" + name,
74 "xyz.openbmc_project.Sensor.Threshold.Warning");
75 }
76 if (thresholds::hasCriticalInterface(thresholds))
77 {
78 thresholdInterfaceCritical = objectServer.add_interface(
79 "/xyz/openbmc_project/sensors/temperature/" + name,
80 "xyz.openbmc_project.Sensor.Threshold.Critical");
81 }
82 setupPowerMatch(conn);
83}
84
85IpmbSensor::~IpmbSensor()
86{
87 waitTimer.cancel();
88 objectServer.remove_interface(thresholdInterfaceWarning);
89 objectServer.remove_interface(thresholdInterfaceCritical);
90 objectServer.remove_interface(sensorInterface);
91}
92
93void IpmbSensor::init(void)
94{
95 setInitialProperties(dbusConnection);
96 loadDefaults();
97 if (initCommand)
98 {
James Feistf7e2c5d2019-02-13 17:27:51 -080099 runInitCmd();
100 }
101 read();
102}
103
104void IpmbSensor::runInitCmd()
105{
106 if (initCommand)
107 {
James Feist6ef20402019-01-07 16:45:08 -0800108 dbusConnection->async_method_call(
109 [this](boost::system::error_code ec,
110 const IpmbMethodType& response) {
111 const int& status = std::get<0>(response);
112
113 if (ec || status)
114 {
115 std::cerr
116 << "Error setting init command for device: " << name
117 << "\n";
118 }
James Feist6ef20402019-01-07 16:45:08 -0800119 },
120 "xyz.openbmc_project.Ipmi.Channel.Ipmb",
121 "/xyz/openbmc_project/Ipmi/Channel/Ipmb", "org.openbmc.Ipmb",
122 "sendRequest", commandAddress, netfn, lun, *initCommand, initData);
123 }
James Feist6ef20402019-01-07 16:45:08 -0800124}
125
126void IpmbSensor::loadDefaults()
127{
128 if (type == IpmbType::meSensor)
129 {
130 commandAddress = meAddress;
131 netfn = 0x4; // sensor
132 command = 0x2d; // get sensor reading
133 commandData = {deviceAddress};
134 }
135 else if (type == IpmbType::PXE1410CVR)
136 {
137 commandAddress = meAddress;
138 netfn = 0x2e; // me bridge
139 command = 0xd9; // send raw pmbus
140 initCommand = 0xd9; // send raw pmbus
141 commandData = {0x57, 0x01, 0x00, 0x16, 0x03, deviceAddress, 00,
142 0x00, 0x00, 0x00, 0x01, 0x02, 0x29};
143 initData = {0x57, 0x01, 0x00, 0x14, 0x03, deviceAddress, 0x00,
144 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x60};
145 }
146 else if (type == IpmbType::IR38363VR)
147 {
148 commandAddress = meAddress;
149 netfn = 0x2e; // me bridge
150 command = 0xd9; // send raw pmbus
151 commandData = {0x57, 0x01, 0x00, 0x16, 0x03, deviceAddress, 00,
152 0x00, 0x00, 0x00, 0x01, 0x02, 0x8D};
153 }
154 else if (type == IpmbType::mpsVR)
155 {
156 commandAddress = meAddress;
157 netfn = 0x2e; // me bridge
158 command = 0xd9; // send raw pmbus
159 initCommand = 0xd9; // send raw pmbus
160 commandData = {0x57, 0x01, 0x00, 0x16, 0x3, deviceAddress, 0x00,
161 0x00, 0x00, 0x00, 0x01, 0x02, 0x8d};
162 initData = {0x57, 0x01, 0x00, 0x14, 0x03, deviceAddress, 0x00,
163 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00};
164 }
165 else
166 {
167 throw std::runtime_error("Invalid sensor type");
168 }
169}
170
171void IpmbSensor::checkThresholds(void)
172{
173 if (readState == PowerState::on && !isPowerOn())
174 {
175 return;
176 }
James Feistfc94b212019-02-06 16:14:51 -0800177 else if (readState == PowerState::biosPost && !hasBiosPost())
178 {
179 return;
180 }
James Feist6ef20402019-01-07 16:45:08 -0800181 thresholds::checkThresholds(this);
182}
183
184void IpmbSensor::read(void)
185{
186 static constexpr size_t pollTime = 1; // in seconds
187
188 waitTimer.expires_from_now(boost::posix_time::seconds(pollTime));
189 waitTimer.async_wait([this](const boost::system::error_code& ec) {
190 if (ec == boost::asio::error::operation_aborted)
191 {
192 return; // we're being canceled
193 }
194 if (!isPowerOn() && readState == PowerState::on)
195 {
196 updateValue(0);
197 read();
198 return;
199 }
200 dbusConnection->async_method_call(
201 [this](boost::system::error_code ec,
202 const IpmbMethodType& response) {
203 const int& status = std::get<0>(response);
James Feist0d4f2bd2019-03-05 13:15:40 -0800204 static bool firstError = true; // don't print too much
James Feist6ef20402019-01-07 16:45:08 -0800205 if (ec || status)
206 {
James Feist0d4f2bd2019-03-05 13:15:40 -0800207 if (firstError)
208 {
209 std::cerr << "Error reading from device: " << name
210 << "\n";
211 firstError = false;
212 }
James Feist6ef20402019-01-07 16:45:08 -0800213 updateValue(0);
214 read();
215 return;
216 }
217 if (!isPowerOn() && readState == PowerState::on)
218 {
219 updateValue(0);
220 read();
221 return;
222 }
223 const std::vector<uint8_t>& data = std::get<5>(response);
224 if constexpr (debug)
225 {
226 std::cout << name << ": ";
227 for (size_t d : data)
228 {
229 std::cout << d << " ";
230 }
231 std::cout << "\n";
232 }
233 uint16_t value = 0;
234 if (type == IpmbType::meSensor)
235 {
236 if (data.empty())
237 {
James Feist0d4f2bd2019-03-05 13:15:40 -0800238 if (firstError)
239 {
240 std::cerr << "Invalid data from device: " << name
241 << "\n";
242 firstError = false;
243 }
James Feist6ef20402019-01-07 16:45:08 -0800244 read();
245 return;
246 }
247 value = data[0];
248 }
249 else if (type == IpmbType::PXE1410CVR ||
250 type == IpmbType::IR38363VR)
251 {
252 if (data.size() < 4)
253 {
James Feist0d4f2bd2019-03-05 13:15:40 -0800254 if (firstError)
255 {
256 std::cerr << "Invalid data from device: " << name
257 << "\n";
258 firstError = false;
259 }
James Feist6ef20402019-01-07 16:45:08 -0800260 read();
261 return;
262 }
263 // format based on the 11 bit linear data format
264 value = ((data[4] << 8) | data[3]) >> 3;
265 }
266 else if (type == IpmbType::mpsVR)
267 {
268 if (data.size() < 4)
269 {
James Feist0d4f2bd2019-03-05 13:15:40 -0800270 if (firstError)
271 {
272 std::cerr << "Invalid data from device: " << name
273 << "\n";
274 firstError = false;
275 }
James Feist6ef20402019-01-07 16:45:08 -0800276 read();
277 return;
278 }
279 value = data[3];
280 }
281 else
282 {
283 throw std::runtime_error("Invalid sensor type");
284 }
285 updateValue(value);
286 read();
James Feist0d4f2bd2019-03-05 13:15:40 -0800287 firstError = true; // success
James Feist6ef20402019-01-07 16:45:08 -0800288 },
289 "xyz.openbmc_project.Ipmi.Channel.Ipmb",
290 "/xyz/openbmc_project/Ipmi/Channel/Ipmb", "org.openbmc.Ipmb",
291 "sendRequest", commandAddress, netfn, lun, command, commandData);
292 });
293}
294void createSensors(
295 boost::asio::io_service& io, sdbusplus::asio::object_server& objectServer,
296 boost::container::flat_map<std::string, std::unique_ptr<IpmbSensor>>&
297 sensors,
298 std::shared_ptr<sdbusplus::asio::connection>& dbusConnection)
299{
300 if (!dbusConnection)
301 {
302 std::cerr << "Connection not created\n";
303 return;
304 }
305 dbusConnection->async_method_call(
306 [&](boost::system::error_code ec, const ManagedObjectType& resp) {
307 if (ec)
308 {
309 std::cerr << "Error contacting entity manager\n";
310 return;
311 }
312 for (const auto& pathPair : resp)
313 {
314 for (const auto& entry : pathPair.second)
315 {
316 if (entry.first != configInterface)
317 {
318 continue;
319 }
320 std::string name =
321 loadVariant<std::string>(entry.second, "Name");
322
323 std::vector<thresholds::Threshold> sensorThresholds;
324 if (!parseThresholdsFromConfig(pathPair.second,
325 sensorThresholds))
326 {
327 std::cerr << "error populating thresholds for " << name
328 << "\n";
329 }
330 uint8_t deviceAddress =
331 loadVariant<uint8_t>(entry.second, "Address");
332
333 std::string sensorClass =
334 loadVariant<std::string>(entry.second, "Class");
335 auto& sensor = sensors[name];
336 sensor = std::make_unique<IpmbSensor>(
337 dbusConnection, io, name, pathPair.first, objectServer,
338 std::move(sensorThresholds), deviceAddress);
339
James Feistfc94b212019-02-06 16:14:51 -0800340 auto findPowerState = entry.second.find("PowerState");
341
342 if (findPowerState != entry.second.end())
343 {
344 std::string powerState = std::visit(
345 VariantToStringVisitor(), findPowerState->second);
346
347 setReadState(powerState, sensor->readState);
348 }
349
James Feist6ef20402019-01-07 16:45:08 -0800350 if (sensorClass == "PxeBridgeTemp")
351 {
352 sensor->type = IpmbType::PXE1410CVR;
353 }
354 else if (sensorClass == "IRBridgeTemp")
355 {
356 sensor->type = IpmbType::IR38363VR;
357 }
358 else if (sensorClass == "MpsBridgeTemp")
359 {
360 sensor->type = IpmbType::mpsVR;
361 }
362 else if (sensorClass == "METemp")
363 {
364 sensor->type = IpmbType::meSensor;
365 }
366 else
367 {
368 std::cerr << "Invalid class " << sensorClass << "\n";
369 continue;
370 }
371 sensor->init();
372 }
373 }
374 },
375 entityManagerName, "/", "org.freedesktop.DBus.ObjectManager",
376 "GetManagedObjects");
377}
378
James Feistf7e2c5d2019-02-13 17:27:51 -0800379void reinitSensors(sdbusplus::message::message& message)
380{
James Feist0d4f2bd2019-03-05 13:15:40 -0800381 constexpr const size_t reinitWaitSeconds = 2;
James Feistf7e2c5d2019-02-13 17:27:51 -0800382 std::string objectName;
383 boost::container::flat_map<std::string, std::variant<int32_t>> values;
384 message.read(objectName, values);
James Feist0d4f2bd2019-03-05 13:15:40 -0800385
James Feistf7e2c5d2019-02-13 17:27:51 -0800386 auto findPgood = values.find("pgood");
387 if (findPgood != values.end())
388 {
389 int32_t powerStatus = std::get<int32_t>(findPgood->second);
390 if (powerStatus)
391 {
James Feist0d4f2bd2019-03-05 13:15:40 -0800392 if (!initCmdTimer)
James Feistf7e2c5d2019-02-13 17:27:51 -0800393 {
James Feist0d4f2bd2019-03-05 13:15:40 -0800394 // this should be impossible
395 return;
James Feistf7e2c5d2019-02-13 17:27:51 -0800396 }
James Feist0d4f2bd2019-03-05 13:15:40 -0800397 // we seem to send this command too fast sometimes, wait before
398 // sending
399 initCmdTimer->expires_from_now(
400 boost::posix_time::seconds(reinitWaitSeconds));
401
402 initCmdTimer->async_wait([](const boost::system::error_code ec) {
403 if (ec == boost::asio::error::operation_aborted)
404 {
405 return; // we're being canceled
406 }
407
408 for (const auto& sensor : sensors)
409 {
410 if (sensor.second)
411 {
412 sensor.second->runInitCmd();
413 }
414 }
415 });
James Feistf7e2c5d2019-02-13 17:27:51 -0800416 }
417 }
418}
419
James Feist6ef20402019-01-07 16:45:08 -0800420int main(int argc, char** argv)
421{
422
423 boost::asio::io_service io;
424 auto systemBus = std::make_shared<sdbusplus::asio::connection>(io);
425 systemBus->request_name("xyz.openbmc_project.IpmbSensor");
426 sdbusplus::asio::object_server objectServer(systemBus);
James Feist6ef20402019-01-07 16:45:08 -0800427
James Feist0d4f2bd2019-03-05 13:15:40 -0800428 initCmdTimer = std::make_unique<boost::asio::deadline_timer>(io);
429
James Feist6ef20402019-01-07 16:45:08 -0800430 io.post([&]() { createSensors(io, objectServer, sensors, systemBus); });
431
432 boost::asio::deadline_timer configTimer(io);
433
434 std::function<void(sdbusplus::message::message&)> eventHandler =
435 [&](sdbusplus::message::message& message) {
436 configTimer.expires_from_now(boost::posix_time::seconds(1));
437 // create a timer because normally multiple properties change
438 configTimer.async_wait([&](const boost::system::error_code& ec) {
439 if (ec == boost::asio::error::operation_aborted)
440 {
441 return; // we're being canceled
442 }
443 createSensors(io, objectServer, sensors, systemBus);
444 if (sensors.empty())
445 {
446 std::cout << "Configuration not detected\n";
447 }
448 });
449 };
450
James Feistf7e2c5d2019-02-13 17:27:51 -0800451 sdbusplus::bus::match::match configMatch(
James Feist6ef20402019-01-07 16:45:08 -0800452 static_cast<sdbusplus::bus::bus&>(*systemBus),
453 "type='signal',member='PropertiesChanged',path_namespace='" +
454 std::string(inventoryPath) + "',arg0namespace='" + configInterface +
455 "'",
456 eventHandler);
457
James Feistf7e2c5d2019-02-13 17:27:51 -0800458 sdbusplus::bus::match::match powerChangeMatch(
459 static_cast<sdbusplus::bus::bus&>(*systemBus),
460 "type='signal',interface='org.freedesktop.DBus.Properties',path_"
461 "namespace='/xyz/openbmc_project/Chassis/Control/"
462 "Power0',arg0='xyz.openbmc_project.Chassis.Control.Power'",
463 reinitSensors);
464
James Feist6ef20402019-01-07 16:45:08 -0800465 io.run();
466}