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