1 /*
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 "IpmbSDRSensor.hpp"
20 #include "SensorPaths.hpp"
21 #include "Thresholds.hpp"
22 #include "Utils.hpp"
23 #include "VariantVisitors.hpp"
24 #include "sensor.hpp"
25
26 #include <boost/asio/error.hpp>
27 #include <boost/asio/io_context.hpp>
28 #include <boost/asio/steady_timer.hpp>
29 #include <boost/container/flat_map.hpp>
30 #include <phosphor-logging/lg2.hpp>
31 #include <sdbusplus/asio/connection.hpp>
32 #include <sdbusplus/asio/object_server.hpp>
33 #include <sdbusplus/message.hpp>
34 #include <sdbusplus/message/native_types.hpp>
35
36 #include <algorithm>
37 #include <array>
38 #include <chrono>
39 #include <cstddef>
40 #include <cstdint>
41 #include <iomanip>
42 #include <iostream>
43 #include <limits>
44 #include <memory>
45 #include <sstream>
46 #include <stdexcept>
47 #include <string>
48 #include <tuple>
49 #include <utility>
50 #include <variant>
51 #include <vector>
52
53 constexpr const bool debug = false;
54
55 static constexpr double ipmbMaxReading = 0xFF;
56 static constexpr double ipmbMinReading = 0;
57
58 static constexpr uint8_t meAddress = 1;
59 static constexpr uint8_t lun = 0;
60 static constexpr uint8_t hostSMbusIndexDefault = 0x03;
61 static constexpr uint8_t ipmbBusIndexDefault = 0;
62 static constexpr float pollRateDefault = 1; // in seconds
63
64 static constexpr const char* sensorPathPrefix = "/xyz/openbmc_project/sensors/";
65
IpmbSensor(std::shared_ptr<sdbusplus::asio::connection> & conn,boost::asio::io_context & io,const std::string & sensorName,const std::string & sensorConfiguration,sdbusplus::asio::object_server & objectServer,std::vector<thresholds::Threshold> && thresholdData,uint8_t deviceAddress,uint8_t hostSMbusIndex,const float pollRate,std::string & sensorTypeName)66 IpmbSensor::IpmbSensor(
67 std::shared_ptr<sdbusplus::asio::connection>& conn,
68 boost::asio::io_context& io, const std::string& sensorName,
69 const std::string& sensorConfiguration,
70 sdbusplus::asio::object_server& objectServer,
71 std::vector<thresholds::Threshold>&& thresholdData, uint8_t deviceAddress,
72 uint8_t hostSMbusIndex, const float pollRate, std::string& sensorTypeName) :
73 Sensor(escapeName(sensorName), std::move(thresholdData),
74 sensorConfiguration, "IpmbSensor", false, false, ipmbMaxReading,
75 ipmbMinReading, conn, PowerState::on),
76 deviceAddress(deviceAddress), hostSMbusIndex(hostSMbusIndex),
77 sensorPollMs(static_cast<int>(pollRate * 1000)), objectServer(objectServer),
78 waitTimer(io)
79 {
80 std::string dbusPath = sensorPathPrefix + sensorTypeName + "/" + name;
81
82 sensorInterface = objectServer.add_interface(
83 dbusPath, "xyz.openbmc_project.Sensor.Value");
84
85 for (const auto& threshold : thresholds)
86 {
87 std::string interface = thresholds::getInterface(threshold.level);
88 thresholdInterfaces[static_cast<size_t>(threshold.level)] =
89 objectServer.add_interface(dbusPath, interface);
90 }
91 association = objectServer.add_interface(dbusPath, association::interface);
92 }
93
~IpmbSensor()94 IpmbSensor::~IpmbSensor()
95 {
96 waitTimer.cancel();
97 for (const auto& iface : thresholdInterfaces)
98 {
99 objectServer.remove_interface(iface);
100 }
101 objectServer.remove_interface(sensorInterface);
102 objectServer.remove_interface(association);
103 }
104
getSubTypeUnits() const105 std::string IpmbSensor::getSubTypeUnits() const
106 {
107 switch (subType)
108 {
109 case IpmbSubType::temp:
110 return sensor_paths::unitDegreesC;
111 case IpmbSubType::curr:
112 return sensor_paths::unitAmperes;
113 case IpmbSubType::power:
114 return sensor_paths::unitWatts;
115 case IpmbSubType::volt:
116 return sensor_paths::unitVolts;
117 case IpmbSubType::util:
118 return sensor_paths::unitPercent;
119 default:
120 throw std::runtime_error("Invalid sensor type");
121 }
122 }
123
init()124 void IpmbSensor::init()
125 {
126 loadDefaults();
127 setInitialProperties(getSubTypeUnits());
128 runInitCmd();
129 read();
130 }
131
initCmdCb(const std::weak_ptr<IpmbSensor> & weakRef,const boost::system::error_code & ec,const IpmbMethodType & response)132 static void initCmdCb(const std::weak_ptr<IpmbSensor>& weakRef,
133 const boost::system::error_code& ec,
134 const IpmbMethodType& response)
135 {
136 std::shared_ptr<IpmbSensor> self = weakRef.lock();
137 if (!self)
138 {
139 return;
140 }
141 const int& status = std::get<0>(response);
142 if (ec || (status != 0))
143 {
144 lg2::error("Error setting init command for device: '{NAME}'", "NAME",
145 self->name);
146 }
147 }
148
runInitCmd()149 void IpmbSensor::runInitCmd()
150 {
151 if (!initCommand.has_value())
152 {
153 return;
154 }
155 dbusConnection->async_method_call(
156 [weakRef{weak_from_this()}](const boost::system::error_code& ec,
157 const IpmbMethodType& response) {
158 initCmdCb(weakRef, ec, response);
159 },
160 "xyz.openbmc_project.Ipmi.Channel.Ipmb",
161 "/xyz/openbmc_project/Ipmi/Channel/Ipmb", "org.openbmc.Ipmb",
162 "sendRequest", commandAddress, netfn, lun, initCommand.value_or(0),
163 initData);
164 }
165
loadDefaults()166 void IpmbSensor::loadDefaults()
167 {
168 if (type == IpmbType::meSensor)
169 {
170 commandAddress = meAddress;
171 netfn = ipmi::sensor::netFn;
172 command = ipmi::sensor::getSensorReading;
173 commandData = {deviceAddress};
174 readingFormat = ReadingFormat::byte0;
175 }
176 else if (type == IpmbType::PXE1410CVR)
177 {
178 commandAddress = meAddress;
179 netfn = ipmi::me_bridge::netFn;
180 command = ipmi::me_bridge::sendRawPmbus;
181 initCommand = ipmi::me_bridge::sendRawPmbus;
182 // pmbus read temp
183 commandData = {0x57, 0x01, 0x00, 0x16, hostSMbusIndex,
184 deviceAddress, 0x00, 0x00, 0x00, 0x00,
185 0x01, 0x02, 0x8d};
186 // goto page 0
187 initData = {0x57, 0x01, 0x00, 0x14, hostSMbusIndex,
188 deviceAddress, 0x00, 0x00, 0x00, 0x00,
189 0x02, 0x00, 0x00, 0x00};
190 readingFormat = ReadingFormat::linearElevenBit;
191 }
192 else if (type == IpmbType::IR38363VR)
193 {
194 commandAddress = meAddress;
195 netfn = ipmi::me_bridge::netFn;
196 command = ipmi::me_bridge::sendRawPmbus;
197 // pmbus read temp
198 commandData = {0x57, 0x01, 0x00, 0x16, hostSMbusIndex,
199 deviceAddress, 00, 0x00, 0x00, 0x00,
200 0x01, 0x02, 0x8D};
201 readingFormat = ReadingFormat::elevenBitShift;
202 }
203 else if (type == IpmbType::ADM1278HSC)
204 {
205 commandAddress = meAddress;
206 uint8_t snsNum = 0;
207 switch (subType)
208 {
209 case IpmbSubType::temp:
210 case IpmbSubType::curr:
211 if (subType == IpmbSubType::temp)
212 {
213 snsNum = 0x8d;
214 }
215 else
216 {
217 snsNum = 0x8c;
218 }
219 netfn = ipmi::me_bridge::netFn;
220 command = ipmi::me_bridge::sendRawPmbus;
221 commandData = {0x57, 0x01, 0x00, 0x86, deviceAddress,
222 0x00, 0x00, 0x01, 0x02, snsNum};
223 readingFormat = ReadingFormat::elevenBit;
224 break;
225 case IpmbSubType::power:
226 case IpmbSubType::volt:
227 netfn = ipmi::sensor::netFn;
228 command = ipmi::sensor::getSensorReading;
229 commandData = {deviceAddress};
230 readingFormat = ReadingFormat::byte0;
231 break;
232 default:
233 throw std::runtime_error("Invalid sensor type");
234 }
235 }
236 else if (type == IpmbType::mpsVR)
237 {
238 commandAddress = meAddress;
239 netfn = ipmi::me_bridge::netFn;
240 command = ipmi::me_bridge::sendRawPmbus;
241 initCommand = ipmi::me_bridge::sendRawPmbus;
242 // pmbus read temp
243 commandData = {0x57, 0x01, 0x00, 0x16, hostSMbusIndex,
244 deviceAddress, 0x00, 0x00, 0x00, 0x00,
245 0x01, 0x02, 0x8d};
246 // goto page 0
247 initData = {0x57, 0x01, 0x00, 0x14, hostSMbusIndex,
248 deviceAddress, 0x00, 0x00, 0x00, 0x00,
249 0x02, 0x00, 0x00, 0x00};
250 readingFormat = ReadingFormat::byte3;
251 }
252 else if (type == IpmbType::SMPro)
253 {
254 // This is an Ampere SMPro reachable via a BMC. For example,
255 // this architecture is used on ADLINK Ampere Altra systems.
256 // See the Ampere Family SoC BMC Interface Specification at
257 // https://amperecomputing.com/customer-connect/products/altra-family-software---firmware
258 // for details of the sensors.
259 commandAddress = 0;
260 netfn = 0x30;
261 command = 0x31;
262 commandData = {0x9e, deviceAddress};
263 switch (subType)
264 {
265 case IpmbSubType::temp:
266 readingFormat = ReadingFormat::nineBit;
267 break;
268 case IpmbSubType::power:
269 readingFormat = ReadingFormat::tenBit;
270 break;
271 case IpmbSubType::curr:
272 case IpmbSubType::volt:
273 readingFormat = ReadingFormat::fifteenBit;
274 break;
275 default:
276 throw std::runtime_error("Invalid sensor type");
277 }
278 }
279 else
280 {
281 throw std::runtime_error("Invalid sensor type");
282 }
283
284 if (subType == IpmbSubType::util)
285 {
286 // Utilization need to be scaled to percent
287 maxValue = 100;
288 minValue = 0;
289 }
290 }
291
checkThresholds()292 void IpmbSensor::checkThresholds()
293 {
294 thresholds::checkThresholds(this);
295 }
296
processReading(ReadingFormat readingFormat,uint8_t command,const std::vector<uint8_t> & data,double & resp,size_t errCount)297 bool IpmbSensor::processReading(ReadingFormat readingFormat, uint8_t command,
298 const std::vector<uint8_t>& data, double& resp,
299 size_t errCount)
300 {
301 switch (readingFormat)
302 {
303 case (ReadingFormat::byte0):
304 {
305 if (command == ipmi::sensor::getSensorReading &&
306 !ipmi::sensor::isValid(data))
307 {
308 return false;
309 }
310 resp = data[0];
311 return true;
312 }
313 case (ReadingFormat::byte3):
314 {
315 if (data.size() < 4)
316 {
317 if (errCount == 0U)
318 {
319 lg2::error("Invalid data length returned");
320 }
321 return false;
322 }
323 resp = data[3];
324 return true;
325 }
326 case (ReadingFormat::nineBit):
327 case (ReadingFormat::tenBit):
328 case (ReadingFormat::fifteenBit):
329 {
330 if (data.size() != 2)
331 {
332 if (errCount == 0U)
333 {
334 lg2::error("Invalid data length returned");
335 }
336 return false;
337 }
338
339 // From the Altra Family SoC BMC Interface Specification:
340 // 0xFFFF – This sensor data is either missing or is not supported
341 // by the device.
342 if ((data[0] == 0xff) && (data[1] == 0xff))
343 {
344 return false;
345 }
346
347 if (readingFormat == ReadingFormat::nineBit)
348 {
349 int16_t value = data[0];
350 if ((data[1] & 0x1) != 0)
351 {
352 // Sign extend to 16 bits
353 value |= 0xFF00;
354 }
355 resp = value;
356 }
357 else if (readingFormat == ReadingFormat::tenBit)
358 {
359 uint16_t value = ((data[1] & 0x3) << 8) + data[0];
360 resp = value;
361 }
362 else if (readingFormat == ReadingFormat::fifteenBit)
363 {
364 uint16_t value = ((data[1] & 0x7F) << 8) + data[0];
365 // Convert mV to V
366 resp = value / 1000.0;
367 }
368
369 return true;
370 }
371 case (ReadingFormat::elevenBit):
372 {
373 if (data.size() < 5)
374 {
375 if (errCount == 0U)
376 {
377 lg2::error("Invalid data length returned");
378 }
379 return false;
380 }
381
382 int16_t value = ((data[4] << 8) | data[3]);
383 resp = value;
384 return true;
385 }
386 case (ReadingFormat::elevenBitShift):
387 {
388 if (data.size() < 5)
389 {
390 if (errCount == 0U)
391 {
392 lg2::error("Invalid data length returned");
393 }
394 return false;
395 }
396
397 resp = ((data[4] << 8) | data[3]) >> 3;
398 return true;
399 }
400 case (ReadingFormat::linearElevenBit):
401 {
402 if (data.size() < 5)
403 {
404 if (errCount == 0U)
405 {
406 lg2::error("Invalid data length returned");
407 }
408 return false;
409 }
410
411 int16_t value = ((data[4] << 8) | data[3]);
412 constexpr const size_t shift = 16 - 11; // 11bit into 16bit
413 value <<= shift;
414 value >>= shift;
415 resp = value;
416 return true;
417 }
418 default:
419 throw std::runtime_error("Invalid reading type");
420 }
421 }
422
ipmbRequestCompletionCb(const boost::system::error_code & ec,const IpmbMethodType & response)423 void IpmbSensor::ipmbRequestCompletionCb(const boost::system::error_code& ec,
424 const IpmbMethodType& response)
425 {
426 const int& status = std::get<0>(response);
427 if (ec || (status != 0))
428 {
429 incrementError();
430 read();
431 return;
432 }
433 const std::vector<uint8_t>& data = std::get<5>(response);
434 if constexpr (debug)
435 {
436 std::ostringstream tempStream;
437 for (int d : data)
438 {
439 tempStream << std::setfill('0') << std::setw(2) << std::hex << d
440 << " ";
441 }
442 lg2::info("'{NAME}': '{DATA}'", "NAME", name, "DATA", tempStream.str());
443 }
444 if (data.empty())
445 {
446 incrementError();
447 read();
448 return;
449 }
450
451 double value = 0;
452
453 if (!processReading(readingFormat, command, data, value, errCount))
454 {
455 incrementError();
456 read();
457 return;
458 }
459
460 // rawValue only used in debug logging
461 // up to 5th byte in data are used to derive value
462 size_t end = std::min(sizeof(uint64_t), data.size());
463 uint64_t rawData = 0;
464 for (size_t i = 0; i < end; i++)
465 {
466 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
467 reinterpret_cast<uint8_t*>(&rawData)[i] = data[i];
468 }
469 rawValue = static_cast<double>(rawData);
470
471 /* Adjust value as per scale and offset */
472 value = (value * scaleVal) + offsetVal;
473 updateValue(value);
474 read();
475 }
476
read()477 void IpmbSensor::read()
478 {
479 waitTimer.expires_after(std::chrono::milliseconds(sensorPollMs));
480 waitTimer.async_wait(
481 [weakRef{weak_from_this()}](const boost::system::error_code& ec) {
482 if (ec == boost::asio::error::operation_aborted)
483 {
484 return; // we're being canceled
485 }
486 std::shared_ptr<IpmbSensor> self = weakRef.lock();
487 if (!self)
488 {
489 return;
490 }
491 self->sendIpmbRequest();
492 });
493 }
494
sendIpmbRequest()495 void IpmbSensor::sendIpmbRequest()
496 {
497 if (!readingStateGood())
498 {
499 updateValue(std::numeric_limits<double>::quiet_NaN());
500 read();
501 return;
502 }
503 dbusConnection->async_method_call(
504 [weakRef{weak_from_this()}](boost::system::error_code ec,
505 const IpmbMethodType& response) {
506 std::shared_ptr<IpmbSensor> self = weakRef.lock();
507 if (!self)
508 {
509 return;
510 }
511 self->ipmbRequestCompletionCb(ec, response);
512 },
513 "xyz.openbmc_project.Ipmi.Channel.Ipmb",
514 "/xyz/openbmc_project/Ipmi/Channel/Ipmb", "org.openbmc.Ipmb",
515 "sendRequest", commandAddress, netfn, lun, command, commandData);
516 }
517
sensorClassType(const std::string & sensorClass)518 bool IpmbSensor::sensorClassType(const std::string& sensorClass)
519 {
520 if (sensorClass == "PxeBridgeTemp")
521 {
522 type = IpmbType::PXE1410CVR;
523 }
524 else if (sensorClass == "IRBridgeTemp")
525 {
526 type = IpmbType::IR38363VR;
527 }
528 else if (sensorClass == "HSCBridge")
529 {
530 type = IpmbType::ADM1278HSC;
531 }
532 else if (sensorClass == "MpsBridgeTemp")
533 {
534 type = IpmbType::mpsVR;
535 }
536 else if (sensorClass == "METemp" || sensorClass == "MESensor")
537 {
538 type = IpmbType::meSensor;
539 }
540 else if (sensorClass == "SMPro")
541 {
542 type = IpmbType::SMPro;
543 }
544 else
545 {
546 lg2::error("Invalid class '{SENSOR}'", "SENSOR", sensorClass);
547 return false;
548 }
549 return true;
550 }
551
sensorSubType(const std::string & sensorTypeName)552 void IpmbSensor::sensorSubType(const std::string& sensorTypeName)
553 {
554 if (sensorTypeName == "voltage")
555 {
556 subType = IpmbSubType::volt;
557 }
558 else if (sensorTypeName == "power")
559 {
560 subType = IpmbSubType::power;
561 }
562 else if (sensorTypeName == "current")
563 {
564 subType = IpmbSubType::curr;
565 }
566 else if (sensorTypeName == "utilization")
567 {
568 subType = IpmbSubType::util;
569 }
570 else
571 {
572 subType = IpmbSubType::temp;
573 }
574 }
575
parseConfigValues(const SensorBaseConfigMap & entry)576 void IpmbSensor::parseConfigValues(const SensorBaseConfigMap& entry)
577 {
578 auto findScaleVal = entry.find("ScaleValue");
579 if (findScaleVal != entry.end())
580 {
581 scaleVal = std::visit(VariantToDoubleVisitor(), findScaleVal->second);
582 }
583
584 auto findOffsetVal = entry.find("OffsetValue");
585 if (findOffsetVal != entry.end())
586 {
587 offsetVal = std::visit(VariantToDoubleVisitor(), findOffsetVal->second);
588 }
589
590 readState = getPowerState(entry);
591 }
592
createSensors(boost::asio::io_context & io,sdbusplus::asio::object_server & objectServer,boost::container::flat_map<std::string,std::shared_ptr<IpmbSensor>> & sensors,std::shared_ptr<sdbusplus::asio::connection> & dbusConnection)593 void createSensors(
594 boost::asio::io_context& io, sdbusplus::asio::object_server& objectServer,
595 boost::container::flat_map<std::string, std::shared_ptr<IpmbSensor>>&
596 sensors,
597 std::shared_ptr<sdbusplus::asio::connection>& dbusConnection)
598 {
599 if (!dbusConnection)
600 {
601 lg2::error("Connection not created");
602 return;
603 }
604 dbusConnection->async_method_call(
605 [&](boost::system::error_code ec, const ManagedObjectType& resp) {
606 if (ec)
607 {
608 lg2::error("Error contacting entity manager");
609 return;
610 }
611 for (const auto& [path, interfaces] : resp)
612 {
613 for (const auto& [intf, cfg] : interfaces)
614 {
615 if (intf != configInterfaceName(sensorType))
616 {
617 continue;
618 }
619 std::string name = loadVariant<std::string>(cfg, "Name");
620
621 std::vector<thresholds::Threshold> sensorThresholds;
622 if (!parseThresholdsFromConfig(interfaces,
623 sensorThresholds))
624 {
625 lg2::error("error populating thresholds '{NAME}'",
626 "NAME", name);
627 }
628 uint8_t deviceAddress =
629 loadVariant<uint8_t>(cfg, "Address");
630
631 std::string sensorClass =
632 loadVariant<std::string>(cfg, "Class");
633
634 uint8_t hostSMbusIndex = hostSMbusIndexDefault;
635 auto findSmType = cfg.find("HostSMbusIndex");
636 if (findSmType != cfg.end())
637 {
638 hostSMbusIndex = std::visit(
639 VariantToUnsignedIntVisitor(), findSmType->second);
640 }
641
642 float pollRate = getPollRate(cfg, pollRateDefault);
643
644 uint8_t ipmbBusIndex = ipmbBusIndexDefault;
645 auto findBusType = cfg.find("Bus");
646 if (findBusType != cfg.end())
647 {
648 ipmbBusIndex = std::visit(VariantToUnsignedIntVisitor(),
649 findBusType->second);
650 lg2::error("Ipmb Bus Index for '{NAME}' is '{INDEX}'",
651 "NAME", name, "INDEX", ipmbBusIndex);
652 }
653
654 /* Default sensor type is "temperature" */
655 std::string sensorTypeName = "temperature";
656 auto findType = cfg.find("SensorType");
657 if (findType != cfg.end())
658 {
659 sensorTypeName = std::visit(VariantToStringVisitor(),
660 findType->second);
661 }
662
663 auto& sensor = sensors[name];
664 sensor = nullptr;
665 sensor = std::make_shared<IpmbSensor>(
666 dbusConnection, io, name, path, objectServer,
667 std::move(sensorThresholds), deviceAddress,
668 hostSMbusIndex, pollRate, sensorTypeName);
669
670 sensor->parseConfigValues(cfg);
671 if (!(sensor->sensorClassType(sensorClass)))
672 {
673 continue;
674 }
675 sensor->sensorSubType(sensorTypeName);
676 sensor->init();
677 }
678 }
679 },
680 entityManagerName, "/xyz/openbmc_project/inventory",
681 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
682 }
683
interfaceRemoved(sdbusplus::message_t & message,boost::container::flat_map<std::string,std::shared_ptr<IpmbSensor>> & sensors)684 void interfaceRemoved(
685 sdbusplus::message_t& message,
686 boost::container::flat_map<std::string, std::shared_ptr<IpmbSensor>>&
687 sensors)
688 {
689 if (message.is_method_error())
690 {
691 lg2::error("interfacesRemoved callback method error");
692 return;
693 }
694
695 sdbusplus::message::object_path removedPath;
696 std::vector<std::string> interfaces;
697
698 message.read(removedPath, interfaces);
699
700 // If the xyz.openbmc_project.Confguration.X interface was removed
701 // for one or more sensors, delete those sensor objects.
702 auto sensorIt = sensors.begin();
703 while (sensorIt != sensors.end())
704 {
705 if ((sensorIt->second->configurationPath == removedPath) &&
706 (std::find(interfaces.begin(), interfaces.end(),
707 configInterfaceName(sdrInterface)) != interfaces.end()))
708 {
709 sensorIt = sensors.erase(sensorIt);
710 }
711 else
712 {
713 sensorIt++;
714 }
715 }
716 }
717