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