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