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