1 /**
2  * Copyright © 2019 IBM 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 #include "config.h"
17 
18 #include "data_interface.hpp"
19 
20 #include "util.hpp"
21 
22 #include <fmt/format.h>
23 
24 #include <fstream>
25 #include <iterator>
26 #include <phosphor-logging/log.hpp>
27 #include <xyz/openbmc_project/State/Boot/Progress/server.hpp>
28 
29 namespace openpower
30 {
31 namespace pels
32 {
33 
34 namespace service_name
35 {
36 constexpr auto objectMapper = "xyz.openbmc_project.ObjectMapper";
37 constexpr auto vpdManager = "com.ibm.VPD.Manager";
38 constexpr auto ledGroupManager = "xyz.openbmc_project.LED.GroupManager";
39 constexpr auto logSetting = "xyz.openbmc_project.Settings";
40 } // namespace service_name
41 
42 namespace object_path
43 {
44 constexpr auto objectMapper = "/xyz/openbmc_project/object_mapper";
45 constexpr auto systemInv = "/xyz/openbmc_project/inventory/system";
46 constexpr auto chassisInv = "/xyz/openbmc_project/inventory/system/chassis";
47 constexpr auto motherBoardInv =
48     "/xyz/openbmc_project/inventory/system/chassis/motherboard";
49 constexpr auto baseInv = "/xyz/openbmc_project/inventory";
50 constexpr auto bmcState = "/xyz/openbmc_project/state/bmc0";
51 constexpr auto chassisState = "/xyz/openbmc_project/state/chassis0";
52 constexpr auto hostState = "/xyz/openbmc_project/state/host0";
53 constexpr auto pldm = "/xyz/openbmc_project/pldm";
54 constexpr auto enableHostPELs =
55     "/xyz/openbmc_project/logging/send_event_logs_to_host";
56 constexpr auto vpdManager = "/com/ibm/VPD/Manager";
57 constexpr auto logSetting = "/xyz/openbmc_project/logging/settings";
58 } // namespace object_path
59 
60 namespace interface
61 {
62 constexpr auto dbusProperty = "org.freedesktop.DBus.Properties";
63 constexpr auto objectMapper = "xyz.openbmc_project.ObjectMapper";
64 constexpr auto invAsset = "xyz.openbmc_project.Inventory.Decorator.Asset";
65 constexpr auto bootProgress = "xyz.openbmc_project.State.Boot.Progress";
66 constexpr auto pldmRequester = "xyz.openbmc_project.PLDM.Requester";
67 constexpr auto enable = "xyz.openbmc_project.Object.Enable";
68 constexpr auto bmcState = "xyz.openbmc_project.State.BMC";
69 constexpr auto chassisState = "xyz.openbmc_project.State.Chassis";
70 constexpr auto hostState = "xyz.openbmc_project.State.Host";
71 constexpr auto invMotherboard =
72     "xyz.openbmc_project.Inventory.Item.Board.Motherboard";
73 constexpr auto viniRecordVPD = "com.ibm.ipzvpd.VINI";
74 constexpr auto vsbpRecordVPD = "com.ibm.ipzvpd.VSBP";
75 constexpr auto locCode = "com.ibm.ipzvpd.Location";
76 constexpr auto compatible =
77     "xyz.openbmc_project.Configuration.IBMCompatibleSystem";
78 constexpr auto vpdManager = "com.ibm.VPD.Manager";
79 constexpr auto ledGroup = "xyz.openbmc_project.Led.Group";
80 constexpr auto operationalStatus =
81     "xyz.openbmc_project.State.Decorator.OperationalStatus";
82 constexpr auto logSetting = "xyz.openbmc_project.Logging.Settings";
83 constexpr auto association = "xyz.openbmc_project.Association.Definitions";
84 constexpr auto dumpEntry = "xyz.openbmc_project.Dump.Entry";
85 constexpr auto dumpProgress = "xyz.openbmc_project.Common.Progress";
86 } // namespace interface
87 
88 using namespace sdbusplus::xyz::openbmc_project::State::Boot::server;
89 using namespace phosphor::logging;
90 
91 std::pair<std::string, std::string>
92     DataInterfaceBase::extractConnectorFromLocCode(
93         const std::string& locationCode)
94 {
95     auto base = locationCode;
96     std::string connector{};
97 
98     auto pos = base.find("-T");
99     if (pos != std::string::npos)
100     {
101         connector = base.substr(pos);
102         base = base.substr(0, pos);
103     }
104 
105     return {base, connector};
106 }
107 
108 DataInterface::DataInterface(sdbusplus::bus::bus& bus) : _bus(bus)
109 {
110     readBMCFWVersion();
111     readServerFWVersion();
112     readBMCFWVersionID();
113 
114     // Watch the BootProgress property
115     _properties.emplace_back(std::make_unique<PropertyWatcher<DataInterface>>(
116         bus, object_path::hostState, interface::bootProgress, "BootProgress",
117         *this, [this](const auto& value) {
118             auto status = Progress::convertProgressStagesFromString(
119                 std::get<std::string>(value));
120 
121             if ((status == Progress::ProgressStages::SystemInitComplete) ||
122                 (status == Progress::ProgressStages::OSStart) ||
123                 (status == Progress::ProgressStages::OSRunning))
124             {
125                 setHostUp(true);
126             }
127             else
128             {
129                 setHostUp(false);
130             }
131         }));
132 
133     // Watch the host PEL enable property
134     _properties.emplace_back(std::make_unique<PropertyWatcher<DataInterface>>(
135         bus, object_path::enableHostPELs, interface::enable, "Enabled", *this,
136         [this](const auto& value) {
137             this->_sendPELsToHost = std::get<bool>(value);
138         }));
139 
140     // Watch the BMCState property
141     _properties.emplace_back(std::make_unique<PropertyWatcher<DataInterface>>(
142         bus, object_path::bmcState, interface::bmcState, "CurrentBMCState",
143         *this, [this](const auto& value) {
144             this->_bmcState = std::get<std::string>(value);
145         }));
146 
147     // Watch the chassis current and requested power state properties
148     _properties.emplace_back(std::make_unique<InterfaceWatcher<DataInterface>>(
149         bus, object_path::chassisState, interface::chassisState, *this,
150         [this](const auto& properties) {
151             auto state = properties.find("CurrentPowerState");
152             if (state != properties.end())
153             {
154                 this->_chassisState = std::get<std::string>(state->second);
155             }
156 
157             auto trans = properties.find("RequestedPowerTransition");
158             if (trans != properties.end())
159             {
160                 this->_chassisTransition = std::get<std::string>(trans->second);
161             }
162         }));
163 
164     // Watch the CurrentHostState property
165     _properties.emplace_back(std::make_unique<PropertyWatcher<DataInterface>>(
166         bus, object_path::hostState, interface::hostState, "CurrentHostState",
167         *this, [this](const auto& value) {
168             this->_hostState = std::get<std::string>(value);
169         }));
170 }
171 
172 DBusPropertyMap
173     DataInterface::getAllProperties(const std::string& service,
174                                     const std::string& objectPath,
175                                     const std::string& interface) const
176 {
177     DBusPropertyMap properties;
178 
179     auto method = _bus.new_method_call(service.c_str(), objectPath.c_str(),
180                                        interface::dbusProperty, "GetAll");
181     method.append(interface);
182     auto reply = _bus.call(method);
183 
184     reply.read(properties);
185 
186     return properties;
187 }
188 
189 void DataInterface::getProperty(const std::string& service,
190                                 const std::string& objectPath,
191                                 const std::string& interface,
192                                 const std::string& property,
193                                 DBusValue& value) const
194 {
195 
196     auto method = _bus.new_method_call(service.c_str(), objectPath.c_str(),
197                                        interface::dbusProperty, "Get");
198     method.append(interface, property);
199     auto reply = _bus.call(method);
200 
201     reply.read(value);
202 }
203 
204 DBusPathList DataInterface::getPaths(const DBusInterfaceList& interfaces) const
205 {
206 
207     auto method = _bus.new_method_call(
208         service_name::objectMapper, object_path::objectMapper,
209         interface::objectMapper, "GetSubTreePaths");
210 
211     method.append(std::string{"/"}, 0, interfaces);
212 
213     auto reply = _bus.call(method);
214 
215     DBusPathList paths;
216     reply.read(paths);
217 
218     return paths;
219 }
220 
221 DBusService DataInterface::getService(const std::string& objectPath,
222                                       const std::string& interface) const
223 {
224     auto method = _bus.new_method_call(service_name::objectMapper,
225                                        object_path::objectMapper,
226                                        interface::objectMapper, "GetObject");
227 
228     method.append(objectPath, std::vector<std::string>({interface}));
229 
230     auto reply = _bus.call(method);
231 
232     std::map<DBusService, DBusInterfaceList> response;
233     reply.read(response);
234 
235     if (!response.empty())
236     {
237         return response.begin()->first;
238     }
239 
240     return std::string{};
241 }
242 
243 void DataInterface::readBMCFWVersion()
244 {
245     _bmcFWVersion =
246         phosphor::logging::util::getOSReleaseValue("VERSION").value_or("");
247 }
248 
249 void DataInterface::readServerFWVersion()
250 {
251     auto value =
252         phosphor::logging::util::getOSReleaseValue("VERSION_ID").value_or("");
253     if ((value != "") && (value.find_last_of(')') != std::string::npos))
254     {
255         std::size_t pos = value.find_first_of('(') + 1;
256         _serverFWVersion = value.substr(pos, value.find_last_of(')') - pos);
257     }
258 }
259 
260 void DataInterface::readBMCFWVersionID()
261 {
262     _bmcFWVersionID =
263         phosphor::logging::util::getOSReleaseValue("VERSION_ID").value_or("");
264 }
265 
266 std::string DataInterface::getMachineTypeModel() const
267 {
268     std::string model;
269     try
270     {
271 
272         auto service = getService(object_path::systemInv, interface::invAsset);
273         if (!service.empty())
274         {
275             DBusValue value;
276             getProperty(service, object_path::systemInv, interface::invAsset,
277                         "Model", value);
278 
279             model = std::get<std::string>(value);
280         }
281     }
282     catch (const std::exception& e)
283     {
284         log<level::WARNING>(fmt::format("Failed reading Model property from "
285                                         "Interface: {} exception: {}",
286                                         interface::invAsset, e.what())
287                                 .c_str());
288     }
289 
290     return model;
291 }
292 
293 std::string DataInterface::getMachineSerialNumber() const
294 {
295     std::string sn;
296     try
297     {
298 
299         auto service = getService(object_path::systemInv, interface::invAsset);
300         if (!service.empty())
301         {
302             DBusValue value;
303             getProperty(service, object_path::systemInv, interface::invAsset,
304                         "SerialNumber", value);
305 
306             sn = std::get<std::string>(value);
307         }
308     }
309     catch (const std::exception& e)
310     {
311         log<level::WARNING>(
312             fmt::format("Failed reading SerialNumber property from "
313                         "Interface: {} exception: {}",
314                         interface::invAsset, e.what())
315                 .c_str());
316     }
317 
318     return sn;
319 }
320 
321 std::string DataInterface::getMotherboardCCIN() const
322 {
323     std::string ccin;
324 
325     try
326     {
327         auto service =
328             getService(object_path::motherBoardInv, interface::viniRecordVPD);
329         if (!service.empty())
330         {
331             DBusValue value;
332             getProperty(service, object_path::motherBoardInv,
333                         interface::viniRecordVPD, "CC", value);
334 
335             auto cc = std::get<std::vector<uint8_t>>(value);
336             ccin = std::string{cc.begin(), cc.end()};
337         }
338     }
339     catch (const std::exception& e)
340     {
341         log<level::WARNING>(
342             fmt::format("Failed reading Motherboard CCIN property from "
343                         "Interface: {} exception: {}",
344                         interface::viniRecordVPD, e.what())
345                 .c_str());
346     }
347 
348     return ccin;
349 }
350 
351 std::vector<uint8_t> DataInterface::getSystemIMKeyword() const
352 {
353     std::vector<uint8_t> systemIM;
354 
355     try
356     {
357         auto service =
358             getService(object_path::motherBoardInv, interface::vsbpRecordVPD);
359         if (!service.empty())
360         {
361             DBusValue value;
362             getProperty(service, object_path::motherBoardInv,
363                         interface::vsbpRecordVPD, "IM", value);
364 
365             systemIM = std::get<std::vector<uint8_t>>(value);
366         }
367     }
368     catch (const std::exception& e)
369     {
370         log<level::WARNING>(
371             fmt::format("Failed reading System IM property from "
372                         "Interface: {} exception: {}",
373                         interface::vsbpRecordVPD, e.what())
374                 .c_str());
375     }
376 
377     return systemIM;
378 }
379 
380 void DataInterface::getHWCalloutFields(const std::string& inventoryPath,
381                                        std::string& fruPartNumber,
382                                        std::string& ccin,
383                                        std::string& serialNumber) const
384 {
385     // For now, attempt to get all of the properties directly on the path
386     // passed in.  In the future, may need to make use of an algorithm
387     // to figure out which inventory objects actually hold these
388     // interfaces in the case of non FRUs, or possibly another service
389     // will provide this info.  Any missing interfaces will result
390     // in exceptions being thrown.
391 
392     auto service = getService(inventoryPath, interface::viniRecordVPD);
393 
394     auto properties =
395         getAllProperties(service, inventoryPath, interface::viniRecordVPD);
396 
397     auto value = std::get<std::vector<uint8_t>>(properties["FN"]);
398     fruPartNumber = std::string{value.begin(), value.end()};
399 
400     value = std::get<std::vector<uint8_t>>(properties["CC"]);
401     ccin = std::string{value.begin(), value.end()};
402 
403     value = std::get<std::vector<uint8_t>>(properties["SN"]);
404     serialNumber = std::string{value.begin(), value.end()};
405 }
406 
407 std::string
408     DataInterface::getLocationCode(const std::string& inventoryPath) const
409 {
410     auto service = getService(inventoryPath, interface::locCode);
411 
412     DBusValue locCode;
413     getProperty(service, inventoryPath, interface::locCode, "LocationCode",
414                 locCode);
415 
416     return std::get<std::string>(locCode);
417 }
418 
419 std::string
420     DataInterface::addLocationCodePrefix(const std::string& locationCode)
421 {
422     static const std::string locationCodePrefix{"Ufcs-"};
423 
424     // Technically there are 2 location code prefixes, Ufcs and Umts, so
425     // if it already starts with a U then don't need to do anything.
426     if (locationCode.front() != 'U')
427     {
428         return locationCodePrefix + locationCode;
429     }
430 
431     return locationCode;
432 }
433 
434 std::string DataInterface::expandLocationCode(const std::string& locationCode,
435                                               uint16_t /*node*/) const
436 {
437     // Location codes for connectors are the location code of the FRU they are
438     // on, plus a '-Tx' segment.  Remove this last segment before expanding it
439     // and then add it back in afterwards.  This way, the connector doesn't have
440     // to be in the model just so that it can be expanded.
441     auto [baseLoc, connectorLoc] = extractConnectorFromLocCode(locationCode);
442 
443     auto method =
444         _bus.new_method_call(service_name::vpdManager, object_path::vpdManager,
445                              interface::vpdManager, "GetExpandedLocationCode");
446 
447     method.append(addLocationCodePrefix(baseLoc), static_cast<uint16_t>(0));
448 
449     auto reply = _bus.call(method);
450 
451     std::string expandedLocationCode;
452     reply.read(expandedLocationCode);
453 
454     if (!connectorLoc.empty())
455     {
456         expandedLocationCode += connectorLoc;
457     }
458 
459     return expandedLocationCode;
460 }
461 
462 std::string
463     DataInterface::getInventoryFromLocCode(const std::string& locationCode,
464                                            uint16_t node, bool expanded) const
465 {
466     std::string methodName = expanded ? "GetFRUsByExpandedLocationCode"
467                                       : "GetFRUsByUnexpandedLocationCode";
468 
469     // Remove the connector segment, if present, so that this method call
470     // returns an inventory path that getHWCalloutFields() can be used with.
471     // (The serial number, etc, aren't stored on the connector in the
472     // inventory, and may not even be modeled.)
473     auto [baseLoc, connectorLoc] = extractConnectorFromLocCode(locationCode);
474 
475     auto method =
476         _bus.new_method_call(service_name::vpdManager, object_path::vpdManager,
477                              interface::vpdManager, methodName.c_str());
478 
479     if (expanded)
480     {
481         method.append(baseLoc);
482     }
483     else
484     {
485         method.append(addLocationCodePrefix(baseLoc), node);
486     }
487 
488     auto reply = _bus.call(method);
489 
490     std::vector<sdbusplus::message::object_path> entries;
491     reply.read(entries);
492 
493     // Get the shortest entry from the paths received, as this
494     // would be the path furthest up the inventory hierarchy so
495     // would be the parent FRU.  There is guaranteed to at least
496     // be one entry if the call didn't fail.
497     std::string shortest{entries[0]};
498 
499     std::for_each(entries.begin(), entries.end(),
500                   [&shortest](const auto& path) {
501                       if (path.str.size() < shortest.size())
502                       {
503                           shortest = path;
504                       }
505                   });
506 
507     return shortest;
508 }
509 
510 void DataInterface::assertLEDGroup(const std::string& ledGroup,
511                                    bool value) const
512 {
513     DBusValue variant = value;
514     auto method =
515         _bus.new_method_call(service_name::ledGroupManager, ledGroup.c_str(),
516                              interface::dbusProperty, "Set");
517     method.append(interface::ledGroup, "Asserted", variant);
518     _bus.call(method);
519 }
520 
521 void DataInterface::setFunctional(const std::string& objectPath,
522                                   bool value) const
523 {
524     DBusValue variant = value;
525     auto service = getService(objectPath, interface::operationalStatus);
526 
527     auto method = _bus.new_method_call(service.c_str(), objectPath.c_str(),
528                                        interface::dbusProperty, "Set");
529 
530     method.append(interface::operationalStatus, "Functional", variant);
531     _bus.call(method);
532 }
533 
534 using AssociationTuple = std::tuple<std::string, std::string, std::string>;
535 using AssociationsProperty = std::vector<AssociationTuple>;
536 
537 void DataInterface::setCriticalAssociation(const std::string& objectPath) const
538 {
539     DBusValue getAssociationValue;
540 
541     auto service = getService(objectPath, interface::association);
542 
543     getProperty(service, objectPath, interface::association, "Associations",
544                 getAssociationValue);
545 
546     auto association = std::get<AssociationsProperty>(getAssociationValue);
547 
548     AssociationTuple critAssociation{
549         "health_rollup", "critical",
550         "/xyz/openbmc_project/inventory/system/chassis"};
551 
552     if (std::find(association.begin(), association.end(), critAssociation) ==
553         association.end())
554     {
555         association.push_back(critAssociation);
556         DBusValue setAssociationValue = association;
557 
558         auto method = _bus.new_method_call(service.c_str(), objectPath.c_str(),
559                                            interface::dbusProperty, "Set");
560 
561         method.append(interface::association, "Associations",
562                       setAssociationValue);
563         _bus.call(method);
564     }
565 }
566 
567 std::vector<std::string> DataInterface::getSystemNames() const
568 {
569     DBusSubTree subtree;
570     DBusValue names;
571 
572     auto method = _bus.new_method_call(service_name::objectMapper,
573                                        object_path::objectMapper,
574                                        interface::objectMapper, "GetSubTree");
575     method.append(std::string{"/"}, 0,
576                   std::vector<std::string>{interface::compatible});
577     auto reply = _bus.call(method);
578 
579     reply.read(subtree);
580     if (subtree.empty())
581     {
582         throw std::runtime_error("Compatible interface not on D-Bus");
583     }
584 
585     const auto& object = *(subtree.begin());
586     const auto& path = object.first;
587     const auto& service = object.second.begin()->first;
588 
589     getProperty(service, path, interface::compatible, "Names", names);
590 
591     return std::get<std::vector<std::string>>(names);
592 }
593 
594 bool DataInterface::getQuiesceOnError() const
595 {
596     bool ret = false;
597 
598     try
599     {
600         auto service =
601             getService(object_path::logSetting, interface::logSetting);
602         if (!service.empty())
603         {
604             DBusValue value;
605             getProperty(service, object_path::logSetting, interface::logSetting,
606                         "QuiesceOnHwError", value);
607 
608             ret = std::get<bool>(value);
609         }
610     }
611     catch (const std::exception& e)
612     {
613         log<level::WARNING>(
614             fmt::format("Failed reading QuiesceOnHwError property from "
615                         "Interface: {} exception: {}",
616                         interface::logSetting, e.what())
617                 .c_str());
618     }
619 
620     return ret;
621 }
622 
623 std::vector<bool>
624     DataInterface::checkDumpStatus(const std::vector<std::string>& type) const
625 {
626     DBusSubTree subtree;
627     std::vector<bool> result(type.size(), false);
628 
629     // Query GetSubTree for the availability of dump interface
630     auto method = _bus.new_method_call(service_name::objectMapper,
631                                        object_path::objectMapper,
632                                        interface::objectMapper, "GetSubTree");
633     method.append(std::string{"/"}, 0,
634                   std::vector<std::string>{interface::dumpEntry});
635     auto reply = _bus.call(method);
636 
637     reply.read(subtree);
638 
639     if (subtree.empty())
640     {
641         return result;
642     }
643 
644     std::vector<bool>::iterator itDumpStatus = result.begin();
645     uint8_t count = 0;
646     for (const auto& [path, serviceInfo] : subtree)
647     {
648         const auto& service = serviceInfo.begin()->first;
649         // Check for dump type on the object path
650         for (const auto& it : type)
651         {
652             if (path.find(it) != std::string::npos)
653             {
654                 DBusValue value, progress;
655 
656                 // If dump type status is already available go for next path
657                 if (*itDumpStatus)
658                 {
659                     break;
660                 }
661 
662                 // Check for valid dump to be available if following
663                 // conditions are met for the dump entry path -
664                 // Offloaded == false and Status == Completed
665                 getProperty(service, path, interface::dumpEntry, "Offloaded",
666                             value);
667                 getProperty(service, path, interface::dumpProgress, "Status",
668                             progress);
669                 auto offload = std::get<bool>(value);
670                 auto status = std::get<std::string>(progress);
671                 if (!offload && (status.find("Completed") != std::string::npos))
672                 {
673                     *itDumpStatus = true;
674                     count++;
675                     if (count >= type.size())
676                     {
677                         return result;
678                     }
679                     break;
680                 }
681             }
682             itDumpStatus++;
683         }
684         itDumpStatus = result.begin();
685     }
686 
687     return result;
688 }
689 
690 } // namespace pels
691 } // namespace openpower
692