blob: cb7b70518860791de83655859b2e11cc66c6d570 [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
Vijay Khemka682a5cb2019-07-18 17:34:03 -070045static constexpr const char* sensorPathPrefix = "/xyz/openbmc_project/sensors/";
46
James Feist6ef20402019-01-07 16:45:08 -080047using IpmbMethodType =
48 std::tuple<int, uint8_t, uint8_t, uint8_t, uint8_t, std::vector<uint8_t>>;
49
James Feistf7e2c5d2019-02-13 17:27:51 -080050boost::container::flat_map<std::string, std::unique_ptr<IpmbSensor>> sensors;
51
James Feist0d4f2bd2019-03-05 13:15:40 -080052std::unique_ptr<boost::asio::deadline_timer> initCmdTimer;
53
James Feist6ef20402019-01-07 16:45:08 -080054IpmbSensor::IpmbSensor(std::shared_ptr<sdbusplus::asio::connection>& conn,
55 boost::asio::io_service& io,
56 const std::string& sensorName,
57 const std::string& sensorConfiguration,
58 sdbusplus::asio::object_server& objectServer,
59 std::vector<thresholds::Threshold>&& thresholdData,
Vijay Khemka682a5cb2019-07-18 17:34:03 -070060 uint8_t deviceAddress, std::string& sensorTypeName) :
James Feist6ef20402019-01-07 16:45:08 -080061 Sensor(boost::replace_all_copy(sensorName, " ", "_"),
James Feist930fcde2019-05-28 12:58:43 -070062 std::move(thresholdData), sensorConfiguration,
63 "xyz.openbmc_project.Configuration.ExitAirTemp", ipmbMaxReading,
64 ipmbMinReading),
James Feist6ef20402019-01-07 16:45:08 -080065 objectServer(objectServer), dbusConnection(conn), waitTimer(io),
James Feist52497fd2019-06-07 13:01:33 -070066 deviceAddress(deviceAddress), readState(PowerState::on)
James Feist6ef20402019-01-07 16:45:08 -080067{
Vijay Khemka682a5cb2019-07-18 17:34:03 -070068 std::string dbusPath = sensorPathPrefix + sensorTypeName + "/" + name;
69
James Feist6ef20402019-01-07 16:45:08 -080070 sensorInterface = objectServer.add_interface(
Vijay Khemka682a5cb2019-07-18 17:34:03 -070071 dbusPath, "xyz.openbmc_project.Sensor.Value");
James Feist6ef20402019-01-07 16:45:08 -080072
73 if (thresholds::hasWarningInterface(thresholds))
74 {
75 thresholdInterfaceWarning = objectServer.add_interface(
Vijay Khemka682a5cb2019-07-18 17:34:03 -070076 dbusPath, "xyz.openbmc_project.Sensor.Threshold.Warning");
James Feist6ef20402019-01-07 16:45:08 -080077 }
78 if (thresholds::hasCriticalInterface(thresholds))
79 {
80 thresholdInterfaceCritical = objectServer.add_interface(
Vijay Khemka682a5cb2019-07-18 17:34:03 -070081 dbusPath, "xyz.openbmc_project.Sensor.Threshold.Critical");
James Feist6ef20402019-01-07 16:45:08 -080082 }
James Feist2adc95c2019-09-30 14:55:28 -070083 association = objectServer.add_interface(dbusPath, association::interface);
James Feist6ef20402019-01-07 16:45:08 -080084 setupPowerMatch(conn);
85}
86
87IpmbSensor::~IpmbSensor()
88{
89 waitTimer.cancel();
90 objectServer.remove_interface(thresholdInterfaceWarning);
91 objectServer.remove_interface(thresholdInterfaceCritical);
92 objectServer.remove_interface(sensorInterface);
James Feist078f2322019-03-08 11:09:05 -080093 objectServer.remove_interface(association);
James Feist6ef20402019-01-07 16:45:08 -080094}
95
96void IpmbSensor::init(void)
97{
98 setInitialProperties(dbusConnection);
99 loadDefaults();
100 if (initCommand)
101 {
James Feistf7e2c5d2019-02-13 17:27:51 -0800102 runInitCmd();
103 }
104 read();
105}
106
107void IpmbSensor::runInitCmd()
108{
109 if (initCommand)
110 {
James Feist6ef20402019-01-07 16:45:08 -0800111 dbusConnection->async_method_call(
112 [this](boost::system::error_code ec,
113 const IpmbMethodType& response) {
114 const int& status = std::get<0>(response);
115
116 if (ec || status)
117 {
118 std::cerr
119 << "Error setting init command for device: " << name
120 << "\n";
121 }
James Feist6ef20402019-01-07 16:45:08 -0800122 },
123 "xyz.openbmc_project.Ipmi.Channel.Ipmb",
124 "/xyz/openbmc_project/Ipmi/Channel/Ipmb", "org.openbmc.Ipmb",
125 "sendRequest", commandAddress, netfn, lun, *initCommand, initData);
126 }
James Feist6ef20402019-01-07 16:45:08 -0800127}
128
129void IpmbSensor::loadDefaults()
130{
131 if (type == IpmbType::meSensor)
132 {
133 commandAddress = meAddress;
134 netfn = 0x4; // sensor
135 command = 0x2d; // get sensor reading
136 commandData = {deviceAddress};
137 }
138 else if (type == IpmbType::PXE1410CVR)
139 {
140 commandAddress = meAddress;
141 netfn = 0x2e; // me bridge
142 command = 0xd9; // send raw pmbus
143 initCommand = 0xd9; // send raw pmbus
144 commandData = {0x57, 0x01, 0x00, 0x16, 0x03, deviceAddress, 00,
145 0x00, 0x00, 0x00, 0x01, 0x02, 0x29};
146 initData = {0x57, 0x01, 0x00, 0x14, 0x03, deviceAddress, 0x00,
147 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x60};
148 }
149 else if (type == IpmbType::IR38363VR)
150 {
151 commandAddress = meAddress;
152 netfn = 0x2e; // me bridge
153 command = 0xd9; // send raw pmbus
154 commandData = {0x57, 0x01, 0x00, 0x16, 0x03, deviceAddress, 00,
155 0x00, 0x00, 0x00, 0x01, 0x02, 0x8D};
156 }
Vijay Khemka682a5cb2019-07-18 17:34:03 -0700157 else if (type == IpmbType::ADM1278HSC)
158 {
159 commandAddress = meAddress;
160 switch (subType)
161 {
162 case IpmbSubType::temp:
163 case IpmbSubType::curr:
164 uint8_t snsNum;
165 if (subType == IpmbSubType::temp)
166 snsNum = 0x8d;
167 else
168 snsNum = 0x8c;
169 netfn = 0x2e; // me bridge
170 command = 0xd9; // send raw pmbus
171 commandData = {0x57, 0x01, 0x00, 0x86, deviceAddress,
172 0x00, 0x00, 0x01, 0x02, snsNum};
173 break;
174 case IpmbSubType::power:
175 case IpmbSubType::volt:
176 netfn = 0x4; // sensor
177 command = 0x2d; // get sensor reading
178 commandData = {deviceAddress};
179 break;
180 default:
181 throw std::runtime_error("Invalid sensor type");
182 }
183 }
James Feist6ef20402019-01-07 16:45:08 -0800184 else if (type == IpmbType::mpsVR)
185 {
186 commandAddress = meAddress;
187 netfn = 0x2e; // me bridge
188 command = 0xd9; // send raw pmbus
189 initCommand = 0xd9; // send raw pmbus
190 commandData = {0x57, 0x01, 0x00, 0x16, 0x3, deviceAddress, 0x00,
191 0x00, 0x00, 0x00, 0x01, 0x02, 0x8d};
192 initData = {0x57, 0x01, 0x00, 0x14, 0x03, deviceAddress, 0x00,
193 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00};
194 }
195 else
196 {
197 throw std::runtime_error("Invalid sensor type");
198 }
199}
200
201void IpmbSensor::checkThresholds(void)
202{
203 if (readState == PowerState::on && !isPowerOn())
204 {
205 return;
206 }
James Feistfc94b212019-02-06 16:14:51 -0800207 else if (readState == PowerState::biosPost && !hasBiosPost())
208 {
209 return;
210 }
James Feist6ef20402019-01-07 16:45:08 -0800211 thresholds::checkThresholds(this);
212}
213
214void IpmbSensor::read(void)
215{
216 static constexpr size_t pollTime = 1; // in seconds
217
218 waitTimer.expires_from_now(boost::posix_time::seconds(pollTime));
219 waitTimer.async_wait([this](const boost::system::error_code& ec) {
220 if (ec == boost::asio::error::operation_aborted)
221 {
222 return; // we're being canceled
223 }
James Feist52497fd2019-06-07 13:01:33 -0700224 if (!isPowerOn() && readState != PowerState::always)
James Feist6ef20402019-01-07 16:45:08 -0800225 {
226 updateValue(0);
227 read();
228 return;
229 }
230 dbusConnection->async_method_call(
231 [this](boost::system::error_code ec,
232 const IpmbMethodType& response) {
233 const int& status = std::get<0>(response);
James Feist0d4f2bd2019-03-05 13:15:40 -0800234 static bool firstError = true; // don't print too much
James Feist6ef20402019-01-07 16:45:08 -0800235 if (ec || status)
236 {
James Feist0d4f2bd2019-03-05 13:15:40 -0800237 if (firstError)
238 {
239 std::cerr << "Error reading from device: " << name
240 << "\n";
241 firstError = false;
242 }
James Feist6ef20402019-01-07 16:45:08 -0800243 updateValue(0);
244 read();
245 return;
246 }
James Feist52497fd2019-06-07 13:01:33 -0700247 if (!isPowerOn() && readState != PowerState::always)
James Feist6ef20402019-01-07 16:45:08 -0800248 {
249 updateValue(0);
250 read();
251 return;
252 }
253 const std::vector<uint8_t>& data = std::get<5>(response);
254 if constexpr (debug)
255 {
256 std::cout << name << ": ";
257 for (size_t d : data)
258 {
259 std::cout << d << " ";
260 }
261 std::cout << "\n";
262 }
Vijay Khemka682a5cb2019-07-18 17:34:03 -0700263 double value = 0;
James Feist6ef20402019-01-07 16:45:08 -0800264 if (type == IpmbType::meSensor)
265 {
266 if (data.empty())
267 {
James Feist0d4f2bd2019-03-05 13:15:40 -0800268 if (firstError)
269 {
270 std::cerr << "Invalid data from device: " << name
271 << "\n";
272 firstError = false;
273 }
James Feist6ef20402019-01-07 16:45:08 -0800274 read();
275 return;
276 }
277 value = data[0];
278 }
279 else if (type == IpmbType::PXE1410CVR ||
280 type == IpmbType::IR38363VR)
281 {
282 if (data.size() < 4)
283 {
James Feist0d4f2bd2019-03-05 13:15:40 -0800284 if (firstError)
285 {
286 std::cerr << "Invalid data from device: " << name
287 << "\n";
288 firstError = false;
289 }
James Feist6ef20402019-01-07 16:45:08 -0800290 read();
291 return;
292 }
293 // format based on the 11 bit linear data format
294 value = ((data[4] << 8) | data[3]) >> 3;
295 }
Vijay Khemka682a5cb2019-07-18 17:34:03 -0700296 else if (type == IpmbType::ADM1278HSC)
297 {
298 if (data.empty())
299 {
300 if (firstError)
301 {
302 std::cerr << "Invalid data from device: " << name
303 << "\n";
304 firstError = false;
305 }
306 read();
307 return;
308 }
309 switch (subType)
310 {
311 case IpmbSubType::temp:
312 case IpmbSubType::curr:
313 // format based on the 11 bit linear data format
314 value = ((data[4] << 8) | data[3]);
315 break;
316 case IpmbSubType::power:
317 case IpmbSubType::volt:
318 value = data[0];
319 break;
320 }
321 }
James Feist6ef20402019-01-07 16:45:08 -0800322 else if (type == IpmbType::mpsVR)
323 {
324 if (data.size() < 4)
325 {
James Feist0d4f2bd2019-03-05 13:15:40 -0800326 if (firstError)
327 {
328 std::cerr << "Invalid data from device: " << name
329 << "\n";
330 firstError = false;
331 }
James Feist6ef20402019-01-07 16:45:08 -0800332 read();
333 return;
334 }
335 value = data[3];
336 }
337 else
338 {
339 throw std::runtime_error("Invalid sensor type");
340 }
Vijay Khemka682a5cb2019-07-18 17:34:03 -0700341
342 /* Adjust value as per scale and offset */
343 value = (value * scaleVal) + offsetVal;
James Feist6ef20402019-01-07 16:45:08 -0800344 updateValue(value);
345 read();
James Feist0d4f2bd2019-03-05 13:15:40 -0800346 firstError = true; // success
James Feist6ef20402019-01-07 16:45:08 -0800347 },
348 "xyz.openbmc_project.Ipmi.Channel.Ipmb",
349 "/xyz/openbmc_project/Ipmi/Channel/Ipmb", "org.openbmc.Ipmb",
350 "sendRequest", commandAddress, netfn, lun, command, commandData);
351 });
352}
353void createSensors(
354 boost::asio::io_service& io, sdbusplus::asio::object_server& objectServer,
355 boost::container::flat_map<std::string, std::unique_ptr<IpmbSensor>>&
356 sensors,
357 std::shared_ptr<sdbusplus::asio::connection>& dbusConnection)
358{
359 if (!dbusConnection)
360 {
361 std::cerr << "Connection not created\n";
362 return;
363 }
364 dbusConnection->async_method_call(
365 [&](boost::system::error_code ec, const ManagedObjectType& resp) {
366 if (ec)
367 {
368 std::cerr << "Error contacting entity manager\n";
369 return;
370 }
371 for (const auto& pathPair : resp)
372 {
373 for (const auto& entry : pathPair.second)
374 {
375 if (entry.first != configInterface)
376 {
377 continue;
378 }
379 std::string name =
380 loadVariant<std::string>(entry.second, "Name");
381
382 std::vector<thresholds::Threshold> sensorThresholds;
383 if (!parseThresholdsFromConfig(pathPair.second,
384 sensorThresholds))
385 {
386 std::cerr << "error populating thresholds for " << name
387 << "\n";
388 }
389 uint8_t deviceAddress =
390 loadVariant<uint8_t>(entry.second, "Address");
391
392 std::string sensorClass =
393 loadVariant<std::string>(entry.second, "Class");
Vijay Khemka682a5cb2019-07-18 17:34:03 -0700394
395 /* Default sensor type is "temperature" */
396 std::string sensorTypeName = "temperature";
397 auto findType = entry.second.find("SensorType");
398 if (findType != entry.second.end())
399 {
400 sensorTypeName = std::visit(VariantToStringVisitor(),
401 findType->second);
402 }
403
James Feist6ef20402019-01-07 16:45:08 -0800404 auto& sensor = sensors[name];
405 sensor = std::make_unique<IpmbSensor>(
406 dbusConnection, io, name, pathPair.first, objectServer,
Vijay Khemka682a5cb2019-07-18 17:34:03 -0700407 std::move(sensorThresholds), deviceAddress,
408 sensorTypeName);
409
410 /* Initialize scale and offset value */
411 sensor->scaleVal = 1;
412 sensor->offsetVal = 0;
413
414 auto findScaleVal = entry.second.find("ScaleValue");
415 if (findScaleVal != entry.second.end())
416 {
417 sensor->scaleVal = std::visit(VariantToDoubleVisitor(),
418 findScaleVal->second);
419 }
420
421 auto findOffsetVal = entry.second.find("OffsetValue");
422 if (findOffsetVal != entry.second.end())
423 {
424 sensor->offsetVal = std::visit(VariantToDoubleVisitor(),
425 findOffsetVal->second);
426 }
James Feist6ef20402019-01-07 16:45:08 -0800427
James Feistfc94b212019-02-06 16:14:51 -0800428 auto findPowerState = entry.second.find("PowerState");
429
430 if (findPowerState != entry.second.end())
431 {
432 std::string powerState = std::visit(
433 VariantToStringVisitor(), findPowerState->second);
434
435 setReadState(powerState, sensor->readState);
436 }
437
James Feist6ef20402019-01-07 16:45:08 -0800438 if (sensorClass == "PxeBridgeTemp")
439 {
440 sensor->type = IpmbType::PXE1410CVR;
441 }
442 else if (sensorClass == "IRBridgeTemp")
443 {
444 sensor->type = IpmbType::IR38363VR;
445 }
Vijay Khemka682a5cb2019-07-18 17:34:03 -0700446 else if (sensorClass == "HSCBridge")
447 {
448 sensor->type = IpmbType::ADM1278HSC;
449 }
James Feist6ef20402019-01-07 16:45:08 -0800450 else if (sensorClass == "MpsBridgeTemp")
451 {
452 sensor->type = IpmbType::mpsVR;
453 }
454 else if (sensorClass == "METemp")
455 {
456 sensor->type = IpmbType::meSensor;
457 }
458 else
459 {
460 std::cerr << "Invalid class " << sensorClass << "\n";
461 continue;
462 }
Vijay Khemka682a5cb2019-07-18 17:34:03 -0700463
464 if (sensorTypeName == "voltage")
465 {
466 sensor->subType = IpmbSubType::volt;
467 }
468 else if (sensorTypeName == "power")
469 {
470 sensor->subType = IpmbSubType::power;
471 }
472 else if (sensorTypeName == "current")
473 {
474 sensor->subType = IpmbSubType::curr;
475 }
476 else
477 {
478 sensor->subType = IpmbSubType::temp;
479 }
James Feist6ef20402019-01-07 16:45:08 -0800480 sensor->init();
481 }
482 }
483 },
484 entityManagerName, "/", "org.freedesktop.DBus.ObjectManager",
485 "GetManagedObjects");
486}
487
James Feistf7e2c5d2019-02-13 17:27:51 -0800488void reinitSensors(sdbusplus::message::message& message)
489{
James Feist0d4f2bd2019-03-05 13:15:40 -0800490 constexpr const size_t reinitWaitSeconds = 2;
James Feistf7e2c5d2019-02-13 17:27:51 -0800491 std::string objectName;
James Feist52497fd2019-06-07 13:01:33 -0700492 boost::container::flat_map<std::string, std::variant<std::string>> values;
James Feistf7e2c5d2019-02-13 17:27:51 -0800493 message.read(objectName, values);
James Feist0d4f2bd2019-03-05 13:15:40 -0800494
James Feist52497fd2019-06-07 13:01:33 -0700495 auto findStatus = values.find(power::property);
496 if (findStatus != values.end())
James Feistf7e2c5d2019-02-13 17:27:51 -0800497 {
James Feist52497fd2019-06-07 13:01:33 -0700498 bool powerStatus = boost::ends_with(
499 std::get<std::string>(findStatus->second), "Running");
James Feistf7e2c5d2019-02-13 17:27:51 -0800500 if (powerStatus)
501 {
James Feist0d4f2bd2019-03-05 13:15:40 -0800502 if (!initCmdTimer)
James Feistf7e2c5d2019-02-13 17:27:51 -0800503 {
James Feist0d4f2bd2019-03-05 13:15:40 -0800504 // this should be impossible
505 return;
James Feistf7e2c5d2019-02-13 17:27:51 -0800506 }
James Feist0d4f2bd2019-03-05 13:15:40 -0800507 // we seem to send this command too fast sometimes, wait before
508 // sending
509 initCmdTimer->expires_from_now(
510 boost::posix_time::seconds(reinitWaitSeconds));
511
512 initCmdTimer->async_wait([](const boost::system::error_code ec) {
513 if (ec == boost::asio::error::operation_aborted)
514 {
515 return; // we're being canceled
516 }
517
518 for (const auto& sensor : sensors)
519 {
520 if (sensor.second)
521 {
522 sensor.second->runInitCmd();
523 }
524 }
525 });
James Feistf7e2c5d2019-02-13 17:27:51 -0800526 }
527 }
528}
529
James Feistb6c0b912019-07-09 12:21:44 -0700530int main()
James Feist6ef20402019-01-07 16:45:08 -0800531{
532
533 boost::asio::io_service io;
534 auto systemBus = std::make_shared<sdbusplus::asio::connection>(io);
535 systemBus->request_name("xyz.openbmc_project.IpmbSensor");
536 sdbusplus::asio::object_server objectServer(systemBus);
James Feist6ef20402019-01-07 16:45:08 -0800537
James Feist0d4f2bd2019-03-05 13:15:40 -0800538 initCmdTimer = std::make_unique<boost::asio::deadline_timer>(io);
539
James Feist6ef20402019-01-07 16:45:08 -0800540 io.post([&]() { createSensors(io, objectServer, sensors, systemBus); });
541
542 boost::asio::deadline_timer configTimer(io);
543
544 std::function<void(sdbusplus::message::message&)> eventHandler =
James Feistb6c0b912019-07-09 12:21:44 -0700545 [&](sdbusplus::message::message&) {
James Feist6ef20402019-01-07 16:45:08 -0800546 configTimer.expires_from_now(boost::posix_time::seconds(1));
547 // create a timer because normally multiple properties change
548 configTimer.async_wait([&](const boost::system::error_code& ec) {
549 if (ec == boost::asio::error::operation_aborted)
550 {
551 return; // we're being canceled
552 }
553 createSensors(io, objectServer, sensors, systemBus);
554 if (sensors.empty())
555 {
556 std::cout << "Configuration not detected\n";
557 }
558 });
559 };
560
James Feistf7e2c5d2019-02-13 17:27:51 -0800561 sdbusplus::bus::match::match configMatch(
James Feist6ef20402019-01-07 16:45:08 -0800562 static_cast<sdbusplus::bus::bus&>(*systemBus),
563 "type='signal',member='PropertiesChanged',path_namespace='" +
564 std::string(inventoryPath) + "',arg0namespace='" + configInterface +
565 "'",
566 eventHandler);
567
James Feistf7e2c5d2019-02-13 17:27:51 -0800568 sdbusplus::bus::match::match powerChangeMatch(
569 static_cast<sdbusplus::bus::bus&>(*systemBus),
James Feist52497fd2019-06-07 13:01:33 -0700570 "type='signal',interface='" + std::string(properties::interface) +
571 "',path='" + std::string(power::path) + "',arg0='" +
572 std::string(power::interface) + "'",
James Feistf7e2c5d2019-02-13 17:27:51 -0800573 reinitSensors);
574
James Feist6ef20402019-01-07 16:45:08 -0800575 io.run();
576}