blob: b0b20ded112a4e44eff53c6a9ef11719a903fd26 [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>
Patrick Venture96e97db2019-10-31 13:44:38 -070027#include <boost/container/flat_map.hpp>
James Feist6ef20402019-01-07 16:45:08 -080028#include <chrono>
Patrick Venture96e97db2019-10-31 13:44:38 -070029#include <functional>
James Feist6ef20402019-01-07 16:45:08 -080030#include <iostream>
31#include <limits>
Patrick Venture96e97db2019-10-31 13:44:38 -070032#include <memory>
James Feist6ef20402019-01-07 16:45:08 -080033#include <numeric>
34#include <sdbusplus/asio/connection.hpp>
35#include <sdbusplus/asio/object_server.hpp>
Patrick Venture96e97db2019-10-31 13:44:38 -070036#include <sdbusplus/bus/match.hpp>
37#include <string>
38#include <tuple>
39#include <variant>
James Feist6ef20402019-01-07 16:45:08 -080040#include <vector>
41
42constexpr const bool debug = false;
43
44constexpr const char* configInterface =
45 "xyz.openbmc_project.Configuration.IpmbSensor";
46static constexpr double ipmbMaxReading = 0xFF;
47static constexpr double ipmbMinReading = 0;
48
49static constexpr uint8_t meAddress = 1;
50static constexpr uint8_t lun = 0;
51
Vijay Khemka682a5cb2019-07-18 17:34:03 -070052static constexpr const char* sensorPathPrefix = "/xyz/openbmc_project/sensors/";
53
James Feist6ef20402019-01-07 16:45:08 -080054using IpmbMethodType =
55 std::tuple<int, uint8_t, uint8_t, uint8_t, uint8_t, std::vector<uint8_t>>;
56
James Feistf7e2c5d2019-02-13 17:27:51 -080057boost::container::flat_map<std::string, std::unique_ptr<IpmbSensor>> sensors;
58
James Feist0d4f2bd2019-03-05 13:15:40 -080059std::unique_ptr<boost::asio::deadline_timer> initCmdTimer;
60
James Feist6ef20402019-01-07 16:45:08 -080061IpmbSensor::IpmbSensor(std::shared_ptr<sdbusplus::asio::connection>& conn,
62 boost::asio::io_service& io,
63 const std::string& sensorName,
64 const std::string& sensorConfiguration,
65 sdbusplus::asio::object_server& objectServer,
66 std::vector<thresholds::Threshold>&& thresholdData,
Vijay Khemka682a5cb2019-07-18 17:34:03 -070067 uint8_t deviceAddress, std::string& sensorTypeName) :
James Feist6ef20402019-01-07 16:45:08 -080068 Sensor(boost::replace_all_copy(sensorName, " ", "_"),
James Feist930fcde2019-05-28 12:58:43 -070069 std::move(thresholdData), sensorConfiguration,
70 "xyz.openbmc_project.Configuration.ExitAirTemp", ipmbMaxReading,
71 ipmbMinReading),
Brad Bishopfbb44ad2019-11-08 09:42:37 -050072 deviceAddress(deviceAddress), readState(PowerState::on),
73 objectServer(objectServer), dbusConnection(conn), waitTimer(io)
James Feist6ef20402019-01-07 16:45:08 -080074{
Vijay Khemka682a5cb2019-07-18 17:34:03 -070075 std::string dbusPath = sensorPathPrefix + sensorTypeName + "/" + name;
76
James Feist6ef20402019-01-07 16:45:08 -080077 sensorInterface = objectServer.add_interface(
Vijay Khemka682a5cb2019-07-18 17:34:03 -070078 dbusPath, "xyz.openbmc_project.Sensor.Value");
James Feist6ef20402019-01-07 16:45:08 -080079
80 if (thresholds::hasWarningInterface(thresholds))
81 {
82 thresholdInterfaceWarning = objectServer.add_interface(
Vijay Khemka682a5cb2019-07-18 17:34:03 -070083 dbusPath, "xyz.openbmc_project.Sensor.Threshold.Warning");
James Feist6ef20402019-01-07 16:45:08 -080084 }
85 if (thresholds::hasCriticalInterface(thresholds))
86 {
87 thresholdInterfaceCritical = objectServer.add_interface(
Vijay Khemka682a5cb2019-07-18 17:34:03 -070088 dbusPath, "xyz.openbmc_project.Sensor.Threshold.Critical");
James Feist6ef20402019-01-07 16:45:08 -080089 }
James Feist2adc95c2019-09-30 14:55:28 -070090 association = objectServer.add_interface(dbusPath, association::interface);
James Feist6ef20402019-01-07 16:45:08 -080091 setupPowerMatch(conn);
92}
93
94IpmbSensor::~IpmbSensor()
95{
96 waitTimer.cancel();
97 objectServer.remove_interface(thresholdInterfaceWarning);
98 objectServer.remove_interface(thresholdInterfaceCritical);
99 objectServer.remove_interface(sensorInterface);
James Feist078f2322019-03-08 11:09:05 -0800100 objectServer.remove_interface(association);
James Feist6ef20402019-01-07 16:45:08 -0800101}
102
103void IpmbSensor::init(void)
104{
105 setInitialProperties(dbusConnection);
106 loadDefaults();
107 if (initCommand)
108 {
James Feistf7e2c5d2019-02-13 17:27:51 -0800109 runInitCmd();
110 }
111 read();
112}
113
114void IpmbSensor::runInitCmd()
115{
116 if (initCommand)
117 {
James Feist6ef20402019-01-07 16:45:08 -0800118 dbusConnection->async_method_call(
119 [this](boost::system::error_code ec,
120 const IpmbMethodType& response) {
121 const int& status = std::get<0>(response);
122
123 if (ec || status)
124 {
125 std::cerr
126 << "Error setting init command for device: " << name
127 << "\n";
128 }
James Feist6ef20402019-01-07 16:45:08 -0800129 },
130 "xyz.openbmc_project.Ipmi.Channel.Ipmb",
131 "/xyz/openbmc_project/Ipmi/Channel/Ipmb", "org.openbmc.Ipmb",
132 "sendRequest", commandAddress, netfn, lun, *initCommand, initData);
133 }
James Feist6ef20402019-01-07 16:45:08 -0800134}
135
136void IpmbSensor::loadDefaults()
137{
138 if (type == IpmbType::meSensor)
139 {
140 commandAddress = meAddress;
141 netfn = 0x4; // sensor
142 command = 0x2d; // get sensor reading
143 commandData = {deviceAddress};
144 }
145 else if (type == IpmbType::PXE1410CVR)
146 {
147 commandAddress = meAddress;
148 netfn = 0x2e; // me bridge
149 command = 0xd9; // send raw pmbus
150 initCommand = 0xd9; // send raw pmbus
151 commandData = {0x57, 0x01, 0x00, 0x16, 0x03, deviceAddress, 00,
152 0x00, 0x00, 0x00, 0x01, 0x02, 0x29};
153 initData = {0x57, 0x01, 0x00, 0x14, 0x03, deviceAddress, 0x00,
154 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x60};
155 }
156 else if (type == IpmbType::IR38363VR)
157 {
158 commandAddress = meAddress;
159 netfn = 0x2e; // me bridge
160 command = 0xd9; // send raw pmbus
161 commandData = {0x57, 0x01, 0x00, 0x16, 0x03, deviceAddress, 00,
162 0x00, 0x00, 0x00, 0x01, 0x02, 0x8D};
163 }
Vijay Khemka682a5cb2019-07-18 17:34:03 -0700164 else if (type == IpmbType::ADM1278HSC)
165 {
166 commandAddress = meAddress;
167 switch (subType)
168 {
169 case IpmbSubType::temp:
170 case IpmbSubType::curr:
171 uint8_t snsNum;
172 if (subType == IpmbSubType::temp)
173 snsNum = 0x8d;
174 else
175 snsNum = 0x8c;
176 netfn = 0x2e; // me bridge
177 command = 0xd9; // send raw pmbus
178 commandData = {0x57, 0x01, 0x00, 0x86, deviceAddress,
179 0x00, 0x00, 0x01, 0x02, snsNum};
180 break;
181 case IpmbSubType::power:
182 case IpmbSubType::volt:
183 netfn = 0x4; // sensor
184 command = 0x2d; // get sensor reading
185 commandData = {deviceAddress};
186 break;
187 default:
188 throw std::runtime_error("Invalid sensor type");
189 }
190 }
James Feist6ef20402019-01-07 16:45:08 -0800191 else if (type == IpmbType::mpsVR)
192 {
193 commandAddress = meAddress;
194 netfn = 0x2e; // me bridge
195 command = 0xd9; // send raw pmbus
196 initCommand = 0xd9; // send raw pmbus
197 commandData = {0x57, 0x01, 0x00, 0x16, 0x3, deviceAddress, 0x00,
198 0x00, 0x00, 0x00, 0x01, 0x02, 0x8d};
199 initData = {0x57, 0x01, 0x00, 0x14, 0x03, deviceAddress, 0x00,
200 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00};
201 }
202 else
203 {
204 throw std::runtime_error("Invalid sensor type");
205 }
206}
207
208void IpmbSensor::checkThresholds(void)
209{
210 if (readState == PowerState::on && !isPowerOn())
211 {
212 return;
213 }
James Feistfc94b212019-02-06 16:14:51 -0800214 else if (readState == PowerState::biosPost && !hasBiosPost())
215 {
216 return;
217 }
James Feist6ef20402019-01-07 16:45:08 -0800218 thresholds::checkThresholds(this);
219}
220
221void IpmbSensor::read(void)
222{
223 static constexpr size_t pollTime = 1; // in seconds
224
225 waitTimer.expires_from_now(boost::posix_time::seconds(pollTime));
226 waitTimer.async_wait([this](const boost::system::error_code& ec) {
227 if (ec == boost::asio::error::operation_aborted)
228 {
229 return; // we're being canceled
230 }
James Feist52497fd2019-06-07 13:01:33 -0700231 if (!isPowerOn() && readState != PowerState::always)
James Feist6ef20402019-01-07 16:45:08 -0800232 {
233 updateValue(0);
234 read();
235 return;
236 }
237 dbusConnection->async_method_call(
238 [this](boost::system::error_code ec,
239 const IpmbMethodType& response) {
240 const int& status = std::get<0>(response);
James Feist0d4f2bd2019-03-05 13:15:40 -0800241 static bool firstError = true; // don't print too much
James Feist6ef20402019-01-07 16:45:08 -0800242 if (ec || status)
243 {
James Feist0d4f2bd2019-03-05 13:15:40 -0800244 if (firstError)
245 {
246 std::cerr << "Error reading from device: " << name
247 << "\n";
248 firstError = false;
249 }
James Feist6ef20402019-01-07 16:45:08 -0800250 updateValue(0);
251 read();
252 return;
253 }
James Feist52497fd2019-06-07 13:01:33 -0700254 if (!isPowerOn() && readState != PowerState::always)
James Feist6ef20402019-01-07 16:45:08 -0800255 {
256 updateValue(0);
257 read();
258 return;
259 }
260 const std::vector<uint8_t>& data = std::get<5>(response);
261 if constexpr (debug)
262 {
263 std::cout << name << ": ";
264 for (size_t d : data)
265 {
266 std::cout << d << " ";
267 }
268 std::cout << "\n";
269 }
Vijay Khemka682a5cb2019-07-18 17:34:03 -0700270 double value = 0;
James Feist6ef20402019-01-07 16:45:08 -0800271 if (type == IpmbType::meSensor)
272 {
273 if (data.empty())
274 {
James Feist0d4f2bd2019-03-05 13:15:40 -0800275 if (firstError)
276 {
277 std::cerr << "Invalid data from device: " << name
278 << "\n";
279 firstError = false;
280 }
James Feist6ef20402019-01-07 16:45:08 -0800281 read();
282 return;
283 }
284 value = data[0];
285 }
286 else if (type == IpmbType::PXE1410CVR ||
287 type == IpmbType::IR38363VR)
288 {
Patrick Venture61bc6dc2019-10-10 09:17:04 -0700289 if (data.size() < 5)
James Feist6ef20402019-01-07 16:45:08 -0800290 {
James Feist0d4f2bd2019-03-05 13:15:40 -0800291 if (firstError)
292 {
293 std::cerr << "Invalid data from device: " << name
294 << "\n";
295 firstError = false;
296 }
James Feist6ef20402019-01-07 16:45:08 -0800297 read();
298 return;
299 }
300 // format based on the 11 bit linear data format
301 value = ((data[4] << 8) | data[3]) >> 3;
302 }
Vijay Khemka682a5cb2019-07-18 17:34:03 -0700303 else if (type == IpmbType::ADM1278HSC)
304 {
305 if (data.empty())
306 {
307 if (firstError)
308 {
309 std::cerr << "Invalid data from device: " << name
310 << "\n";
311 firstError = false;
312 }
313 read();
314 return;
315 }
316 switch (subType)
317 {
318 case IpmbSubType::temp:
319 case IpmbSubType::curr:
320 // format based on the 11 bit linear data format
321 value = ((data[4] << 8) | data[3]);
322 break;
323 case IpmbSubType::power:
324 case IpmbSubType::volt:
325 value = data[0];
326 break;
327 }
328 }
James Feist6ef20402019-01-07 16:45:08 -0800329 else if (type == IpmbType::mpsVR)
330 {
331 if (data.size() < 4)
332 {
James Feist0d4f2bd2019-03-05 13:15:40 -0800333 if (firstError)
334 {
335 std::cerr << "Invalid data from device: " << name
336 << "\n";
337 firstError = false;
338 }
James Feist6ef20402019-01-07 16:45:08 -0800339 read();
340 return;
341 }
342 value = data[3];
343 }
344 else
345 {
346 throw std::runtime_error("Invalid sensor type");
347 }
Vijay Khemka682a5cb2019-07-18 17:34:03 -0700348
349 /* Adjust value as per scale and offset */
350 value = (value * scaleVal) + offsetVal;
James Feist6ef20402019-01-07 16:45:08 -0800351 updateValue(value);
352 read();
James Feist0d4f2bd2019-03-05 13:15:40 -0800353 firstError = true; // success
James Feist6ef20402019-01-07 16:45:08 -0800354 },
355 "xyz.openbmc_project.Ipmi.Channel.Ipmb",
356 "/xyz/openbmc_project/Ipmi/Channel/Ipmb", "org.openbmc.Ipmb",
357 "sendRequest", commandAddress, netfn, lun, command, commandData);
358 });
359}
360void createSensors(
361 boost::asio::io_service& io, sdbusplus::asio::object_server& objectServer,
362 boost::container::flat_map<std::string, std::unique_ptr<IpmbSensor>>&
363 sensors,
364 std::shared_ptr<sdbusplus::asio::connection>& dbusConnection)
365{
366 if (!dbusConnection)
367 {
368 std::cerr << "Connection not created\n";
369 return;
370 }
371 dbusConnection->async_method_call(
372 [&](boost::system::error_code ec, const ManagedObjectType& resp) {
373 if (ec)
374 {
375 std::cerr << "Error contacting entity manager\n";
376 return;
377 }
378 for (const auto& pathPair : resp)
379 {
380 for (const auto& entry : pathPair.second)
381 {
382 if (entry.first != configInterface)
383 {
384 continue;
385 }
386 std::string name =
387 loadVariant<std::string>(entry.second, "Name");
388
389 std::vector<thresholds::Threshold> sensorThresholds;
390 if (!parseThresholdsFromConfig(pathPair.second,
391 sensorThresholds))
392 {
393 std::cerr << "error populating thresholds for " << name
394 << "\n";
395 }
396 uint8_t deviceAddress =
397 loadVariant<uint8_t>(entry.second, "Address");
398
399 std::string sensorClass =
400 loadVariant<std::string>(entry.second, "Class");
Vijay Khemka682a5cb2019-07-18 17:34:03 -0700401
402 /* Default sensor type is "temperature" */
403 std::string sensorTypeName = "temperature";
404 auto findType = entry.second.find("SensorType");
405 if (findType != entry.second.end())
406 {
407 sensorTypeName = std::visit(VariantToStringVisitor(),
408 findType->second);
409 }
410
James Feist6ef20402019-01-07 16:45:08 -0800411 auto& sensor = sensors[name];
412 sensor = std::make_unique<IpmbSensor>(
413 dbusConnection, io, name, pathPair.first, objectServer,
Vijay Khemka682a5cb2019-07-18 17:34:03 -0700414 std::move(sensorThresholds), deviceAddress,
415 sensorTypeName);
416
417 /* Initialize scale and offset value */
418 sensor->scaleVal = 1;
419 sensor->offsetVal = 0;
420
421 auto findScaleVal = entry.second.find("ScaleValue");
422 if (findScaleVal != entry.second.end())
423 {
424 sensor->scaleVal = std::visit(VariantToDoubleVisitor(),
425 findScaleVal->second);
426 }
427
428 auto findOffsetVal = entry.second.find("OffsetValue");
429 if (findOffsetVal != entry.second.end())
430 {
431 sensor->offsetVal = std::visit(VariantToDoubleVisitor(),
432 findOffsetVal->second);
433 }
James Feist6ef20402019-01-07 16:45:08 -0800434
James Feistfc94b212019-02-06 16:14:51 -0800435 auto findPowerState = entry.second.find("PowerState");
436
437 if (findPowerState != entry.second.end())
438 {
439 std::string powerState = std::visit(
440 VariantToStringVisitor(), findPowerState->second);
441
442 setReadState(powerState, sensor->readState);
443 }
444
James Feist6ef20402019-01-07 16:45:08 -0800445 if (sensorClass == "PxeBridgeTemp")
446 {
447 sensor->type = IpmbType::PXE1410CVR;
448 }
449 else if (sensorClass == "IRBridgeTemp")
450 {
451 sensor->type = IpmbType::IR38363VR;
452 }
Vijay Khemka682a5cb2019-07-18 17:34:03 -0700453 else if (sensorClass == "HSCBridge")
454 {
455 sensor->type = IpmbType::ADM1278HSC;
456 }
James Feist6ef20402019-01-07 16:45:08 -0800457 else if (sensorClass == "MpsBridgeTemp")
458 {
459 sensor->type = IpmbType::mpsVR;
460 }
461 else if (sensorClass == "METemp")
462 {
463 sensor->type = IpmbType::meSensor;
464 }
465 else
466 {
467 std::cerr << "Invalid class " << sensorClass << "\n";
468 continue;
469 }
Vijay Khemka682a5cb2019-07-18 17:34:03 -0700470
471 if (sensorTypeName == "voltage")
472 {
473 sensor->subType = IpmbSubType::volt;
474 }
475 else if (sensorTypeName == "power")
476 {
477 sensor->subType = IpmbSubType::power;
478 }
479 else if (sensorTypeName == "current")
480 {
481 sensor->subType = IpmbSubType::curr;
482 }
483 else
484 {
485 sensor->subType = IpmbSubType::temp;
486 }
James Feist6ef20402019-01-07 16:45:08 -0800487 sensor->init();
488 }
489 }
490 },
491 entityManagerName, "/", "org.freedesktop.DBus.ObjectManager",
492 "GetManagedObjects");
493}
494
James Feistf7e2c5d2019-02-13 17:27:51 -0800495void reinitSensors(sdbusplus::message::message& message)
496{
James Feist0d4f2bd2019-03-05 13:15:40 -0800497 constexpr const size_t reinitWaitSeconds = 2;
James Feistf7e2c5d2019-02-13 17:27:51 -0800498 std::string objectName;
James Feist52497fd2019-06-07 13:01:33 -0700499 boost::container::flat_map<std::string, std::variant<std::string>> values;
James Feistf7e2c5d2019-02-13 17:27:51 -0800500 message.read(objectName, values);
James Feist0d4f2bd2019-03-05 13:15:40 -0800501
James Feist52497fd2019-06-07 13:01:33 -0700502 auto findStatus = values.find(power::property);
503 if (findStatus != values.end())
James Feistf7e2c5d2019-02-13 17:27:51 -0800504 {
James Feist52497fd2019-06-07 13:01:33 -0700505 bool powerStatus = boost::ends_with(
506 std::get<std::string>(findStatus->second), "Running");
James Feistf7e2c5d2019-02-13 17:27:51 -0800507 if (powerStatus)
508 {
James Feist0d4f2bd2019-03-05 13:15:40 -0800509 if (!initCmdTimer)
James Feistf7e2c5d2019-02-13 17:27:51 -0800510 {
James Feist0d4f2bd2019-03-05 13:15:40 -0800511 // this should be impossible
512 return;
James Feistf7e2c5d2019-02-13 17:27:51 -0800513 }
James Feist0d4f2bd2019-03-05 13:15:40 -0800514 // we seem to send this command too fast sometimes, wait before
515 // sending
516 initCmdTimer->expires_from_now(
517 boost::posix_time::seconds(reinitWaitSeconds));
518
519 initCmdTimer->async_wait([](const boost::system::error_code ec) {
520 if (ec == boost::asio::error::operation_aborted)
521 {
522 return; // we're being canceled
523 }
524
525 for (const auto& sensor : sensors)
526 {
527 if (sensor.second)
528 {
529 sensor.second->runInitCmd();
530 }
531 }
532 });
James Feistf7e2c5d2019-02-13 17:27:51 -0800533 }
534 }
535}
536
James Feistb6c0b912019-07-09 12:21:44 -0700537int main()
James Feist6ef20402019-01-07 16:45:08 -0800538{
539
540 boost::asio::io_service io;
541 auto systemBus = std::make_shared<sdbusplus::asio::connection>(io);
542 systemBus->request_name("xyz.openbmc_project.IpmbSensor");
543 sdbusplus::asio::object_server objectServer(systemBus);
James Feist6ef20402019-01-07 16:45:08 -0800544
James Feist0d4f2bd2019-03-05 13:15:40 -0800545 initCmdTimer = std::make_unique<boost::asio::deadline_timer>(io);
546
James Feist6ef20402019-01-07 16:45:08 -0800547 io.post([&]() { createSensors(io, objectServer, sensors, systemBus); });
548
549 boost::asio::deadline_timer configTimer(io);
550
551 std::function<void(sdbusplus::message::message&)> eventHandler =
James Feistb6c0b912019-07-09 12:21:44 -0700552 [&](sdbusplus::message::message&) {
James Feist6ef20402019-01-07 16:45:08 -0800553 configTimer.expires_from_now(boost::posix_time::seconds(1));
554 // create a timer because normally multiple properties change
555 configTimer.async_wait([&](const boost::system::error_code& ec) {
556 if (ec == boost::asio::error::operation_aborted)
557 {
558 return; // we're being canceled
559 }
560 createSensors(io, objectServer, sensors, systemBus);
561 if (sensors.empty())
562 {
563 std::cout << "Configuration not detected\n";
564 }
565 });
566 };
567
James Feistf7e2c5d2019-02-13 17:27:51 -0800568 sdbusplus::bus::match::match configMatch(
James Feist6ef20402019-01-07 16:45:08 -0800569 static_cast<sdbusplus::bus::bus&>(*systemBus),
570 "type='signal',member='PropertiesChanged',path_namespace='" +
571 std::string(inventoryPath) + "',arg0namespace='" + configInterface +
572 "'",
573 eventHandler);
574
James Feistf7e2c5d2019-02-13 17:27:51 -0800575 sdbusplus::bus::match::match powerChangeMatch(
576 static_cast<sdbusplus::bus::bus&>(*systemBus),
James Feist52497fd2019-06-07 13:01:33 -0700577 "type='signal',interface='" + std::string(properties::interface) +
578 "',path='" + std::string(power::path) + "',arg0='" +
579 std::string(power::interface) + "'",
James Feistf7e2c5d2019-02-13 17:27:51 -0800580 reinitSensors);
581
James Feist6ef20402019-01-07 16:45:08 -0800582 io.run();
583}