blob: 9f591ac81e13ac2943857fe5eb47473445b978cf [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
James Feist551087a2019-12-09 11:17:12 -0800221void IpmbSensor::processError(void)
222{
223 static constexpr size_t errorFilter = 2;
224
225 if (!errorCount)
226 {
227 std::cerr << "Invalid data from device: " << name << "\n";
228 }
229 else if (errorCount > errorFilter)
230 {
231 updateValue(0);
232 }
233 if (errorCount < std::numeric_limits<uint8_t>::max())
234 {
235 errorCount++;
236 }
237}
238
James Feist6ef20402019-01-07 16:45:08 -0800239void IpmbSensor::read(void)
240{
241 static constexpr size_t pollTime = 1; // in seconds
242
243 waitTimer.expires_from_now(boost::posix_time::seconds(pollTime));
244 waitTimer.async_wait([this](const boost::system::error_code& ec) {
245 if (ec == boost::asio::error::operation_aborted)
246 {
247 return; // we're being canceled
248 }
James Feist52497fd2019-06-07 13:01:33 -0700249 if (!isPowerOn() && readState != PowerState::always)
James Feist6ef20402019-01-07 16:45:08 -0800250 {
251 updateValue(0);
252 read();
253 return;
254 }
255 dbusConnection->async_method_call(
256 [this](boost::system::error_code ec,
257 const IpmbMethodType& response) {
258 const int& status = std::get<0>(response);
259 if (ec || status)
260 {
James Feist551087a2019-12-09 11:17:12 -0800261 processError();
James Feist6ef20402019-01-07 16:45:08 -0800262 updateValue(0);
263 read();
264 return;
265 }
James Feist52497fd2019-06-07 13:01:33 -0700266 if (!isPowerOn() && readState != PowerState::always)
James Feist6ef20402019-01-07 16:45:08 -0800267 {
268 updateValue(0);
269 read();
270 return;
271 }
272 const std::vector<uint8_t>& data = std::get<5>(response);
273 if constexpr (debug)
274 {
275 std::cout << name << ": ";
276 for (size_t d : data)
277 {
278 std::cout << d << " ";
279 }
280 std::cout << "\n";
281 }
Vijay Khemka682a5cb2019-07-18 17:34:03 -0700282 double value = 0;
James Feist6ef20402019-01-07 16:45:08 -0800283 if (type == IpmbType::meSensor)
284 {
285 if (data.empty())
286 {
James Feist551087a2019-12-09 11:17:12 -0800287 processError();
James Feist6ef20402019-01-07 16:45:08 -0800288 read();
289 return;
290 }
291 value = data[0];
292 }
293 else if (type == IpmbType::PXE1410CVR ||
294 type == IpmbType::IR38363VR)
295 {
Patrick Venture61bc6dc2019-10-10 09:17:04 -0700296 if (data.size() < 5)
James Feist6ef20402019-01-07 16:45:08 -0800297 {
James Feist551087a2019-12-09 11:17:12 -0800298 processError();
James Feist6ef20402019-01-07 16:45:08 -0800299 read();
300 return;
301 }
302 // format based on the 11 bit linear data format
303 value = ((data[4] << 8) | data[3]) >> 3;
304 }
Vijay Khemka682a5cb2019-07-18 17:34:03 -0700305 else if (type == IpmbType::ADM1278HSC)
306 {
307 if (data.empty())
308 {
James Feist551087a2019-12-09 11:17:12 -0800309 processError();
Vijay Khemka682a5cb2019-07-18 17:34:03 -0700310 read();
311 return;
312 }
313 switch (subType)
314 {
315 case IpmbSubType::temp:
316 case IpmbSubType::curr:
317 // format based on the 11 bit linear data format
318 value = ((data[4] << 8) | data[3]);
319 break;
320 case IpmbSubType::power:
321 case IpmbSubType::volt:
322 value = data[0];
323 break;
324 }
325 }
James Feist6ef20402019-01-07 16:45:08 -0800326 else if (type == IpmbType::mpsVR)
327 {
328 if (data.size() < 4)
329 {
James Feist551087a2019-12-09 11:17:12 -0800330 processError();
James Feist6ef20402019-01-07 16:45:08 -0800331 read();
332 return;
333 }
334 value = data[3];
335 }
336 else
337 {
338 throw std::runtime_error("Invalid sensor type");
339 }
Vijay Khemka682a5cb2019-07-18 17:34:03 -0700340
341 /* Adjust value as per scale and offset */
342 value = (value * scaleVal) + offsetVal;
James Feist6ef20402019-01-07 16:45:08 -0800343 updateValue(value);
344 read();
James Feist551087a2019-12-09 11:17:12 -0800345 errorCount = 0; // success
James Feist6ef20402019-01-07 16:45:08 -0800346 },
347 "xyz.openbmc_project.Ipmi.Channel.Ipmb",
348 "/xyz/openbmc_project/Ipmi/Channel/Ipmb", "org.openbmc.Ipmb",
349 "sendRequest", commandAddress, netfn, lun, command, commandData);
350 });
351}
352void createSensors(
353 boost::asio::io_service& io, sdbusplus::asio::object_server& objectServer,
354 boost::container::flat_map<std::string, std::unique_ptr<IpmbSensor>>&
355 sensors,
356 std::shared_ptr<sdbusplus::asio::connection>& dbusConnection)
357{
358 if (!dbusConnection)
359 {
360 std::cerr << "Connection not created\n";
361 return;
362 }
363 dbusConnection->async_method_call(
364 [&](boost::system::error_code ec, const ManagedObjectType& resp) {
365 if (ec)
366 {
367 std::cerr << "Error contacting entity manager\n";
368 return;
369 }
370 for (const auto& pathPair : resp)
371 {
372 for (const auto& entry : pathPair.second)
373 {
374 if (entry.first != configInterface)
375 {
376 continue;
377 }
378 std::string name =
379 loadVariant<std::string>(entry.second, "Name");
380
381 std::vector<thresholds::Threshold> sensorThresholds;
382 if (!parseThresholdsFromConfig(pathPair.second,
383 sensorThresholds))
384 {
385 std::cerr << "error populating thresholds for " << name
386 << "\n";
387 }
388 uint8_t deviceAddress =
389 loadVariant<uint8_t>(entry.second, "Address");
390
391 std::string sensorClass =
392 loadVariant<std::string>(entry.second, "Class");
Vijay Khemka682a5cb2019-07-18 17:34:03 -0700393
394 /* Default sensor type is "temperature" */
395 std::string sensorTypeName = "temperature";
396 auto findType = entry.second.find("SensorType");
397 if (findType != entry.second.end())
398 {
399 sensorTypeName = std::visit(VariantToStringVisitor(),
400 findType->second);
401 }
402
James Feist6ef20402019-01-07 16:45:08 -0800403 auto& sensor = sensors[name];
404 sensor = std::make_unique<IpmbSensor>(
405 dbusConnection, io, name, pathPair.first, objectServer,
Vijay Khemka682a5cb2019-07-18 17:34:03 -0700406 std::move(sensorThresholds), deviceAddress,
407 sensorTypeName);
408
409 /* Initialize scale and offset value */
410 sensor->scaleVal = 1;
411 sensor->offsetVal = 0;
412
413 auto findScaleVal = entry.second.find("ScaleValue");
414 if (findScaleVal != entry.second.end())
415 {
416 sensor->scaleVal = std::visit(VariantToDoubleVisitor(),
417 findScaleVal->second);
418 }
419
420 auto findOffsetVal = entry.second.find("OffsetValue");
421 if (findOffsetVal != entry.second.end())
422 {
423 sensor->offsetVal = std::visit(VariantToDoubleVisitor(),
424 findOffsetVal->second);
425 }
James Feist6ef20402019-01-07 16:45:08 -0800426
James Feistfc94b212019-02-06 16:14:51 -0800427 auto findPowerState = entry.second.find("PowerState");
428
429 if (findPowerState != entry.second.end())
430 {
431 std::string powerState = std::visit(
432 VariantToStringVisitor(), findPowerState->second);
433
434 setReadState(powerState, sensor->readState);
435 }
436
James Feist6ef20402019-01-07 16:45:08 -0800437 if (sensorClass == "PxeBridgeTemp")
438 {
439 sensor->type = IpmbType::PXE1410CVR;
440 }
441 else if (sensorClass == "IRBridgeTemp")
442 {
443 sensor->type = IpmbType::IR38363VR;
444 }
Vijay Khemka682a5cb2019-07-18 17:34:03 -0700445 else if (sensorClass == "HSCBridge")
446 {
447 sensor->type = IpmbType::ADM1278HSC;
448 }
James Feist6ef20402019-01-07 16:45:08 -0800449 else if (sensorClass == "MpsBridgeTemp")
450 {
451 sensor->type = IpmbType::mpsVR;
452 }
453 else if (sensorClass == "METemp")
454 {
455 sensor->type = IpmbType::meSensor;
456 }
457 else
458 {
459 std::cerr << "Invalid class " << sensorClass << "\n";
460 continue;
461 }
Vijay Khemka682a5cb2019-07-18 17:34:03 -0700462
463 if (sensorTypeName == "voltage")
464 {
465 sensor->subType = IpmbSubType::volt;
466 }
467 else if (sensorTypeName == "power")
468 {
469 sensor->subType = IpmbSubType::power;
470 }
471 else if (sensorTypeName == "current")
472 {
473 sensor->subType = IpmbSubType::curr;
474 }
475 else
476 {
477 sensor->subType = IpmbSubType::temp;
478 }
James Feist6ef20402019-01-07 16:45:08 -0800479 sensor->init();
480 }
481 }
482 },
483 entityManagerName, "/", "org.freedesktop.DBus.ObjectManager",
484 "GetManagedObjects");
485}
486
James Feistf7e2c5d2019-02-13 17:27:51 -0800487void reinitSensors(sdbusplus::message::message& message)
488{
James Feist0d4f2bd2019-03-05 13:15:40 -0800489 constexpr const size_t reinitWaitSeconds = 2;
James Feistf7e2c5d2019-02-13 17:27:51 -0800490 std::string objectName;
James Feist52497fd2019-06-07 13:01:33 -0700491 boost::container::flat_map<std::string, std::variant<std::string>> values;
James Feistf7e2c5d2019-02-13 17:27:51 -0800492 message.read(objectName, values);
James Feist0d4f2bd2019-03-05 13:15:40 -0800493
James Feist52497fd2019-06-07 13:01:33 -0700494 auto findStatus = values.find(power::property);
495 if (findStatus != values.end())
James Feistf7e2c5d2019-02-13 17:27:51 -0800496 {
James Feist52497fd2019-06-07 13:01:33 -0700497 bool powerStatus = boost::ends_with(
498 std::get<std::string>(findStatus->second), "Running");
James Feistf7e2c5d2019-02-13 17:27:51 -0800499 if (powerStatus)
500 {
James Feist0d4f2bd2019-03-05 13:15:40 -0800501 if (!initCmdTimer)
James Feistf7e2c5d2019-02-13 17:27:51 -0800502 {
James Feist0d4f2bd2019-03-05 13:15:40 -0800503 // this should be impossible
504 return;
James Feistf7e2c5d2019-02-13 17:27:51 -0800505 }
James Feist0d4f2bd2019-03-05 13:15:40 -0800506 // we seem to send this command too fast sometimes, wait before
507 // sending
508 initCmdTimer->expires_from_now(
509 boost::posix_time::seconds(reinitWaitSeconds));
510
511 initCmdTimer->async_wait([](const boost::system::error_code ec) {
512 if (ec == boost::asio::error::operation_aborted)
513 {
514 return; // we're being canceled
515 }
516
517 for (const auto& sensor : sensors)
518 {
519 if (sensor.second)
520 {
521 sensor.second->runInitCmd();
522 }
523 }
524 });
James Feistf7e2c5d2019-02-13 17:27:51 -0800525 }
526 }
527}
528
James Feistb6c0b912019-07-09 12:21:44 -0700529int main()
James Feist6ef20402019-01-07 16:45:08 -0800530{
531
532 boost::asio::io_service io;
533 auto systemBus = std::make_shared<sdbusplus::asio::connection>(io);
534 systemBus->request_name("xyz.openbmc_project.IpmbSensor");
535 sdbusplus::asio::object_server objectServer(systemBus);
James Feist6ef20402019-01-07 16:45:08 -0800536
James Feist0d4f2bd2019-03-05 13:15:40 -0800537 initCmdTimer = std::make_unique<boost::asio::deadline_timer>(io);
538
James Feist6ef20402019-01-07 16:45:08 -0800539 io.post([&]() { createSensors(io, objectServer, sensors, systemBus); });
540
541 boost::asio::deadline_timer configTimer(io);
542
543 std::function<void(sdbusplus::message::message&)> eventHandler =
James Feistb6c0b912019-07-09 12:21:44 -0700544 [&](sdbusplus::message::message&) {
James Feist6ef20402019-01-07 16:45:08 -0800545 configTimer.expires_from_now(boost::posix_time::seconds(1));
546 // create a timer because normally multiple properties change
547 configTimer.async_wait([&](const boost::system::error_code& ec) {
548 if (ec == boost::asio::error::operation_aborted)
549 {
550 return; // we're being canceled
551 }
552 createSensors(io, objectServer, sensors, systemBus);
553 if (sensors.empty())
554 {
555 std::cout << "Configuration not detected\n";
556 }
557 });
558 };
559
James Feistf7e2c5d2019-02-13 17:27:51 -0800560 sdbusplus::bus::match::match configMatch(
James Feist6ef20402019-01-07 16:45:08 -0800561 static_cast<sdbusplus::bus::bus&>(*systemBus),
562 "type='signal',member='PropertiesChanged',path_namespace='" +
563 std::string(inventoryPath) + "',arg0namespace='" + configInterface +
564 "'",
565 eventHandler);
566
James Feistf7e2c5d2019-02-13 17:27:51 -0800567 sdbusplus::bus::match::match powerChangeMatch(
568 static_cast<sdbusplus::bus::bus&>(*systemBus),
James Feist52497fd2019-06-07 13:01:33 -0700569 "type='signal',interface='" + std::string(properties::interface) +
570 "',path='" + std::string(power::path) + "',arg0='" +
571 std::string(power::interface) + "'",
James Feistf7e2c5d2019-02-13 17:27:51 -0800572 reinitSensors);
573
James Feist6ef20402019-01-07 16:45:08 -0800574 io.run();
575}