blob: 946c06041a27e22566bfadfbac1105e0e8c93acb [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 }
Vijay Khemka682a5cb2019-07-18 17:34:03 -070083 association =
84 objectServer.add_interface(dbusPath, "org.openbmc.Associations");
James Feist6ef20402019-01-07 16:45:08 -080085 setupPowerMatch(conn);
86}
87
88IpmbSensor::~IpmbSensor()
89{
90 waitTimer.cancel();
91 objectServer.remove_interface(thresholdInterfaceWarning);
92 objectServer.remove_interface(thresholdInterfaceCritical);
93 objectServer.remove_interface(sensorInterface);
James Feist078f2322019-03-08 11:09:05 -080094 objectServer.remove_interface(association);
James Feist6ef20402019-01-07 16:45:08 -080095}
96
97void IpmbSensor::init(void)
98{
99 setInitialProperties(dbusConnection);
100 loadDefaults();
101 if (initCommand)
102 {
James Feistf7e2c5d2019-02-13 17:27:51 -0800103 runInitCmd();
104 }
105 read();
106}
107
108void IpmbSensor::runInitCmd()
109{
110 if (initCommand)
111 {
James Feist6ef20402019-01-07 16:45:08 -0800112 dbusConnection->async_method_call(
113 [this](boost::system::error_code ec,
114 const IpmbMethodType& response) {
115 const int& status = std::get<0>(response);
116
117 if (ec || status)
118 {
119 std::cerr
120 << "Error setting init command for device: " << name
121 << "\n";
122 }
James Feist6ef20402019-01-07 16:45:08 -0800123 },
124 "xyz.openbmc_project.Ipmi.Channel.Ipmb",
125 "/xyz/openbmc_project/Ipmi/Channel/Ipmb", "org.openbmc.Ipmb",
126 "sendRequest", commandAddress, netfn, lun, *initCommand, initData);
127 }
James Feist6ef20402019-01-07 16:45:08 -0800128}
129
130void IpmbSensor::loadDefaults()
131{
132 if (type == IpmbType::meSensor)
133 {
134 commandAddress = meAddress;
135 netfn = 0x4; // sensor
136 command = 0x2d; // get sensor reading
137 commandData = {deviceAddress};
138 }
139 else if (type == IpmbType::PXE1410CVR)
140 {
141 commandAddress = meAddress;
142 netfn = 0x2e; // me bridge
143 command = 0xd9; // send raw pmbus
144 initCommand = 0xd9; // send raw pmbus
145 commandData = {0x57, 0x01, 0x00, 0x16, 0x03, deviceAddress, 00,
146 0x00, 0x00, 0x00, 0x01, 0x02, 0x29};
147 initData = {0x57, 0x01, 0x00, 0x14, 0x03, deviceAddress, 0x00,
148 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x60};
149 }
150 else if (type == IpmbType::IR38363VR)
151 {
152 commandAddress = meAddress;
153 netfn = 0x2e; // me bridge
154 command = 0xd9; // send raw pmbus
155 commandData = {0x57, 0x01, 0x00, 0x16, 0x03, deviceAddress, 00,
156 0x00, 0x00, 0x00, 0x01, 0x02, 0x8D};
157 }
Vijay Khemka682a5cb2019-07-18 17:34:03 -0700158 else if (type == IpmbType::ADM1278HSC)
159 {
160 commandAddress = meAddress;
161 switch (subType)
162 {
163 case IpmbSubType::temp:
164 case IpmbSubType::curr:
165 uint8_t snsNum;
166 if (subType == IpmbSubType::temp)
167 snsNum = 0x8d;
168 else
169 snsNum = 0x8c;
170 netfn = 0x2e; // me bridge
171 command = 0xd9; // send raw pmbus
172 commandData = {0x57, 0x01, 0x00, 0x86, deviceAddress,
173 0x00, 0x00, 0x01, 0x02, snsNum};
174 break;
175 case IpmbSubType::power:
176 case IpmbSubType::volt:
177 netfn = 0x4; // sensor
178 command = 0x2d; // get sensor reading
179 commandData = {deviceAddress};
180 break;
181 default:
182 throw std::runtime_error("Invalid sensor type");
183 }
184 }
James Feist6ef20402019-01-07 16:45:08 -0800185 else if (type == IpmbType::mpsVR)
186 {
187 commandAddress = meAddress;
188 netfn = 0x2e; // me bridge
189 command = 0xd9; // send raw pmbus
190 initCommand = 0xd9; // send raw pmbus
191 commandData = {0x57, 0x01, 0x00, 0x16, 0x3, deviceAddress, 0x00,
192 0x00, 0x00, 0x00, 0x01, 0x02, 0x8d};
193 initData = {0x57, 0x01, 0x00, 0x14, 0x03, deviceAddress, 0x00,
194 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00};
195 }
196 else
197 {
198 throw std::runtime_error("Invalid sensor type");
199 }
200}
201
202void IpmbSensor::checkThresholds(void)
203{
204 if (readState == PowerState::on && !isPowerOn())
205 {
206 return;
207 }
James Feistfc94b212019-02-06 16:14:51 -0800208 else if (readState == PowerState::biosPost && !hasBiosPost())
209 {
210 return;
211 }
James Feist6ef20402019-01-07 16:45:08 -0800212 thresholds::checkThresholds(this);
213}
214
215void IpmbSensor::read(void)
216{
217 static constexpr size_t pollTime = 1; // in seconds
218
219 waitTimer.expires_from_now(boost::posix_time::seconds(pollTime));
220 waitTimer.async_wait([this](const boost::system::error_code& ec) {
221 if (ec == boost::asio::error::operation_aborted)
222 {
223 return; // we're being canceled
224 }
James Feist52497fd2019-06-07 13:01:33 -0700225 if (!isPowerOn() && readState != PowerState::always)
James Feist6ef20402019-01-07 16:45:08 -0800226 {
227 updateValue(0);
228 read();
229 return;
230 }
231 dbusConnection->async_method_call(
232 [this](boost::system::error_code ec,
233 const IpmbMethodType& response) {
234 const int& status = std::get<0>(response);
James Feist0d4f2bd2019-03-05 13:15:40 -0800235 static bool firstError = true; // don't print too much
James Feist6ef20402019-01-07 16:45:08 -0800236 if (ec || status)
237 {
James Feist0d4f2bd2019-03-05 13:15:40 -0800238 if (firstError)
239 {
240 std::cerr << "Error reading from device: " << name
241 << "\n";
242 firstError = false;
243 }
James Feist6ef20402019-01-07 16:45:08 -0800244 updateValue(0);
245 read();
246 return;
247 }
James Feist52497fd2019-06-07 13:01:33 -0700248 if (!isPowerOn() && readState != PowerState::always)
James Feist6ef20402019-01-07 16:45:08 -0800249 {
250 updateValue(0);
251 read();
252 return;
253 }
254 const std::vector<uint8_t>& data = std::get<5>(response);
255 if constexpr (debug)
256 {
257 std::cout << name << ": ";
258 for (size_t d : data)
259 {
260 std::cout << d << " ";
261 }
262 std::cout << "\n";
263 }
Vijay Khemka682a5cb2019-07-18 17:34:03 -0700264 double value = 0;
James Feist6ef20402019-01-07 16:45:08 -0800265 if (type == IpmbType::meSensor)
266 {
267 if (data.empty())
268 {
James Feist0d4f2bd2019-03-05 13:15:40 -0800269 if (firstError)
270 {
271 std::cerr << "Invalid data from device: " << name
272 << "\n";
273 firstError = false;
274 }
James Feist6ef20402019-01-07 16:45:08 -0800275 read();
276 return;
277 }
278 value = data[0];
279 }
280 else if (type == IpmbType::PXE1410CVR ||
281 type == IpmbType::IR38363VR)
282 {
283 if (data.size() < 4)
284 {
James Feist0d4f2bd2019-03-05 13:15:40 -0800285 if (firstError)
286 {
287 std::cerr << "Invalid data from device: " << name
288 << "\n";
289 firstError = false;
290 }
James Feist6ef20402019-01-07 16:45:08 -0800291 read();
292 return;
293 }
294 // format based on the 11 bit linear data format
295 value = ((data[4] << 8) | data[3]) >> 3;
296 }
Vijay Khemka682a5cb2019-07-18 17:34:03 -0700297 else if (type == IpmbType::ADM1278HSC)
298 {
299 if (data.empty())
300 {
301 if (firstError)
302 {
303 std::cerr << "Invalid data from device: " << name
304 << "\n";
305 firstError = false;
306 }
307 read();
308 return;
309 }
310 switch (subType)
311 {
312 case IpmbSubType::temp:
313 case IpmbSubType::curr:
314 // format based on the 11 bit linear data format
315 value = ((data[4] << 8) | data[3]);
316 break;
317 case IpmbSubType::power:
318 case IpmbSubType::volt:
319 value = data[0];
320 break;
321 }
322 }
James Feist6ef20402019-01-07 16:45:08 -0800323 else if (type == IpmbType::mpsVR)
324 {
325 if (data.size() < 4)
326 {
James Feist0d4f2bd2019-03-05 13:15:40 -0800327 if (firstError)
328 {
329 std::cerr << "Invalid data from device: " << name
330 << "\n";
331 firstError = false;
332 }
James Feist6ef20402019-01-07 16:45:08 -0800333 read();
334 return;
335 }
336 value = data[3];
337 }
338 else
339 {
340 throw std::runtime_error("Invalid sensor type");
341 }
Vijay Khemka682a5cb2019-07-18 17:34:03 -0700342
343 /* Adjust value as per scale and offset */
344 value = (value * scaleVal) + offsetVal;
James Feist6ef20402019-01-07 16:45:08 -0800345 updateValue(value);
346 read();
James Feist0d4f2bd2019-03-05 13:15:40 -0800347 firstError = true; // success
James Feist6ef20402019-01-07 16:45:08 -0800348 },
349 "xyz.openbmc_project.Ipmi.Channel.Ipmb",
350 "/xyz/openbmc_project/Ipmi/Channel/Ipmb", "org.openbmc.Ipmb",
351 "sendRequest", commandAddress, netfn, lun, command, commandData);
352 });
353}
354void createSensors(
355 boost::asio::io_service& io, sdbusplus::asio::object_server& objectServer,
356 boost::container::flat_map<std::string, std::unique_ptr<IpmbSensor>>&
357 sensors,
358 std::shared_ptr<sdbusplus::asio::connection>& dbusConnection)
359{
360 if (!dbusConnection)
361 {
362 std::cerr << "Connection not created\n";
363 return;
364 }
365 dbusConnection->async_method_call(
366 [&](boost::system::error_code ec, const ManagedObjectType& resp) {
367 if (ec)
368 {
369 std::cerr << "Error contacting entity manager\n";
370 return;
371 }
372 for (const auto& pathPair : resp)
373 {
374 for (const auto& entry : pathPair.second)
375 {
376 if (entry.first != configInterface)
377 {
378 continue;
379 }
380 std::string name =
381 loadVariant<std::string>(entry.second, "Name");
382
383 std::vector<thresholds::Threshold> sensorThresholds;
384 if (!parseThresholdsFromConfig(pathPair.second,
385 sensorThresholds))
386 {
387 std::cerr << "error populating thresholds for " << name
388 << "\n";
389 }
390 uint8_t deviceAddress =
391 loadVariant<uint8_t>(entry.second, "Address");
392
393 std::string sensorClass =
394 loadVariant<std::string>(entry.second, "Class");
Vijay Khemka682a5cb2019-07-18 17:34:03 -0700395
396 /* Default sensor type is "temperature" */
397 std::string sensorTypeName = "temperature";
398 auto findType = entry.second.find("SensorType");
399 if (findType != entry.second.end())
400 {
401 sensorTypeName = std::visit(VariantToStringVisitor(),
402 findType->second);
403 }
404
James Feist6ef20402019-01-07 16:45:08 -0800405 auto& sensor = sensors[name];
406 sensor = std::make_unique<IpmbSensor>(
407 dbusConnection, io, name, pathPair.first, objectServer,
Vijay Khemka682a5cb2019-07-18 17:34:03 -0700408 std::move(sensorThresholds), deviceAddress,
409 sensorTypeName);
410
411 /* Initialize scale and offset value */
412 sensor->scaleVal = 1;
413 sensor->offsetVal = 0;
414
415 auto findScaleVal = entry.second.find("ScaleValue");
416 if (findScaleVal != entry.second.end())
417 {
418 sensor->scaleVal = std::visit(VariantToDoubleVisitor(),
419 findScaleVal->second);
420 }
421
422 auto findOffsetVal = entry.second.find("OffsetValue");
423 if (findOffsetVal != entry.second.end())
424 {
425 sensor->offsetVal = std::visit(VariantToDoubleVisitor(),
426 findOffsetVal->second);
427 }
James Feist6ef20402019-01-07 16:45:08 -0800428
James Feistfc94b212019-02-06 16:14:51 -0800429 auto findPowerState = entry.second.find("PowerState");
430
431 if (findPowerState != entry.second.end())
432 {
433 std::string powerState = std::visit(
434 VariantToStringVisitor(), findPowerState->second);
435
436 setReadState(powerState, sensor->readState);
437 }
438
James Feist6ef20402019-01-07 16:45:08 -0800439 if (sensorClass == "PxeBridgeTemp")
440 {
441 sensor->type = IpmbType::PXE1410CVR;
442 }
443 else if (sensorClass == "IRBridgeTemp")
444 {
445 sensor->type = IpmbType::IR38363VR;
446 }
Vijay Khemka682a5cb2019-07-18 17:34:03 -0700447 else if (sensorClass == "HSCBridge")
448 {
449 sensor->type = IpmbType::ADM1278HSC;
450 }
James Feist6ef20402019-01-07 16:45:08 -0800451 else if (sensorClass == "MpsBridgeTemp")
452 {
453 sensor->type = IpmbType::mpsVR;
454 }
455 else if (sensorClass == "METemp")
456 {
457 sensor->type = IpmbType::meSensor;
458 }
459 else
460 {
461 std::cerr << "Invalid class " << sensorClass << "\n";
462 continue;
463 }
Vijay Khemka682a5cb2019-07-18 17:34:03 -0700464
465 if (sensorTypeName == "voltage")
466 {
467 sensor->subType = IpmbSubType::volt;
468 }
469 else if (sensorTypeName == "power")
470 {
471 sensor->subType = IpmbSubType::power;
472 }
473 else if (sensorTypeName == "current")
474 {
475 sensor->subType = IpmbSubType::curr;
476 }
477 else
478 {
479 sensor->subType = IpmbSubType::temp;
480 }
James Feist6ef20402019-01-07 16:45:08 -0800481 sensor->init();
482 }
483 }
484 },
485 entityManagerName, "/", "org.freedesktop.DBus.ObjectManager",
486 "GetManagedObjects");
487}
488
James Feistf7e2c5d2019-02-13 17:27:51 -0800489void reinitSensors(sdbusplus::message::message& message)
490{
James Feist0d4f2bd2019-03-05 13:15:40 -0800491 constexpr const size_t reinitWaitSeconds = 2;
James Feistf7e2c5d2019-02-13 17:27:51 -0800492 std::string objectName;
James Feist52497fd2019-06-07 13:01:33 -0700493 boost::container::flat_map<std::string, std::variant<std::string>> values;
James Feistf7e2c5d2019-02-13 17:27:51 -0800494 message.read(objectName, values);
James Feist0d4f2bd2019-03-05 13:15:40 -0800495
James Feist52497fd2019-06-07 13:01:33 -0700496 auto findStatus = values.find(power::property);
497 if (findStatus != values.end())
James Feistf7e2c5d2019-02-13 17:27:51 -0800498 {
James Feist52497fd2019-06-07 13:01:33 -0700499 bool powerStatus = boost::ends_with(
500 std::get<std::string>(findStatus->second), "Running");
James Feistf7e2c5d2019-02-13 17:27:51 -0800501 if (powerStatus)
502 {
James Feist0d4f2bd2019-03-05 13:15:40 -0800503 if (!initCmdTimer)
James Feistf7e2c5d2019-02-13 17:27:51 -0800504 {
James Feist0d4f2bd2019-03-05 13:15:40 -0800505 // this should be impossible
506 return;
James Feistf7e2c5d2019-02-13 17:27:51 -0800507 }
James Feist0d4f2bd2019-03-05 13:15:40 -0800508 // we seem to send this command too fast sometimes, wait before
509 // sending
510 initCmdTimer->expires_from_now(
511 boost::posix_time::seconds(reinitWaitSeconds));
512
513 initCmdTimer->async_wait([](const boost::system::error_code ec) {
514 if (ec == boost::asio::error::operation_aborted)
515 {
516 return; // we're being canceled
517 }
518
519 for (const auto& sensor : sensors)
520 {
521 if (sensor.second)
522 {
523 sensor.second->runInitCmd();
524 }
525 }
526 });
James Feistf7e2c5d2019-02-13 17:27:51 -0800527 }
528 }
529}
530
James Feistb6c0b912019-07-09 12:21:44 -0700531int main()
James Feist6ef20402019-01-07 16:45:08 -0800532{
533
534 boost::asio::io_service io;
535 auto systemBus = std::make_shared<sdbusplus::asio::connection>(io);
536 systemBus->request_name("xyz.openbmc_project.IpmbSensor");
537 sdbusplus::asio::object_server objectServer(systemBus);
James Feist6ef20402019-01-07 16:45:08 -0800538
James Feist0d4f2bd2019-03-05 13:15:40 -0800539 initCmdTimer = std::make_unique<boost::asio::deadline_timer>(io);
540
James Feist6ef20402019-01-07 16:45:08 -0800541 io.post([&]() { createSensors(io, objectServer, sensors, systemBus); });
542
543 boost::asio::deadline_timer configTimer(io);
544
545 std::function<void(sdbusplus::message::message&)> eventHandler =
James Feistb6c0b912019-07-09 12:21:44 -0700546 [&](sdbusplus::message::message&) {
James Feist6ef20402019-01-07 16:45:08 -0800547 configTimer.expires_from_now(boost::posix_time::seconds(1));
548 // create a timer because normally multiple properties change
549 configTimer.async_wait([&](const boost::system::error_code& ec) {
550 if (ec == boost::asio::error::operation_aborted)
551 {
552 return; // we're being canceled
553 }
554 createSensors(io, objectServer, sensors, systemBus);
555 if (sensors.empty())
556 {
557 std::cout << "Configuration not detected\n";
558 }
559 });
560 };
561
James Feistf7e2c5d2019-02-13 17:27:51 -0800562 sdbusplus::bus::match::match configMatch(
James Feist6ef20402019-01-07 16:45:08 -0800563 static_cast<sdbusplus::bus::bus&>(*systemBus),
564 "type='signal',member='PropertiesChanged',path_namespace='" +
565 std::string(inventoryPath) + "',arg0namespace='" + configInterface +
566 "'",
567 eventHandler);
568
James Feistf7e2c5d2019-02-13 17:27:51 -0800569 sdbusplus::bus::match::match powerChangeMatch(
570 static_cast<sdbusplus::bus::bus&>(*systemBus),
James Feist52497fd2019-06-07 13:01:33 -0700571 "type='signal',interface='" + std::string(properties::interface) +
572 "',path='" + std::string(power::path) + "',arg0='" +
573 std::string(power::interface) + "'",
James Feistf7e2c5d2019-02-13 17:27:51 -0800574 reinitSensors);
575
James Feist6ef20402019-01-07 16:45:08 -0800576 io.run();
577}