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 } // namespace interface
83 
84 using namespace sdbusplus::xyz::openbmc_project::State::Boot::server;
85 using sdbusplus::exception::SdBusError;
86 using namespace phosphor::logging;
87 
88 DataInterface::DataInterface(sdbusplus::bus::bus& bus) : _bus(bus)
89 {
90     readBMCFWVersion();
91     readServerFWVersion();
92     readBMCFWVersionID();
93 
94     // Watch the BootProgress property
95     _properties.emplace_back(std::make_unique<PropertyWatcher<DataInterface>>(
96         bus, object_path::hostState, interface::bootProgress, "BootProgress",
97         *this, [this](const auto& value) {
98             auto status = Progress::convertProgressStagesFromString(
99                 std::get<std::string>(value));
100 
101             if ((status == Progress::ProgressStages::SystemInitComplete) ||
102                 (status == Progress::ProgressStages::OSStart) ||
103                 (status == Progress::ProgressStages::OSRunning))
104             {
105                 setHostUp(true);
106             }
107             else
108             {
109                 setHostUp(false);
110             }
111         }));
112 
113     // Watch the host PEL enable property
114     _properties.emplace_back(std::make_unique<PropertyWatcher<DataInterface>>(
115         bus, object_path::enableHostPELs, interface::enable, "Enabled", *this,
116         [this](const auto& value) {
117             this->_sendPELsToHost = std::get<bool>(value);
118         }));
119 
120     // Watch the BMCState property
121     _properties.emplace_back(std::make_unique<PropertyWatcher<DataInterface>>(
122         bus, object_path::bmcState, interface::bmcState, "CurrentBMCState",
123         *this, [this](const auto& value) {
124             this->_bmcState = std::get<std::string>(value);
125         }));
126 
127     // Watch the chassis current and requested power state properties
128     _properties.emplace_back(std::make_unique<InterfaceWatcher<DataInterface>>(
129         bus, object_path::chassisState, interface::chassisState, *this,
130         [this](const auto& properties) {
131             auto state = properties.find("CurrentPowerState");
132             if (state != properties.end())
133             {
134                 this->_chassisState = std::get<std::string>(state->second);
135             }
136 
137             auto trans = properties.find("RequestedPowerTransition");
138             if (trans != properties.end())
139             {
140                 this->_chassisTransition = std::get<std::string>(trans->second);
141             }
142         }));
143 
144     // Watch the CurrentHostState property
145     _properties.emplace_back(std::make_unique<PropertyWatcher<DataInterface>>(
146         bus, object_path::hostState, interface::hostState, "CurrentHostState",
147         *this, [this](const auto& value) {
148             this->_hostState = std::get<std::string>(value);
149         }));
150 }
151 
152 DBusPropertyMap
153     DataInterface::getAllProperties(const std::string& service,
154                                     const std::string& objectPath,
155                                     const std::string& interface) const
156 {
157     DBusPropertyMap properties;
158 
159     auto method = _bus.new_method_call(service.c_str(), objectPath.c_str(),
160                                        interface::dbusProperty, "GetAll");
161     method.append(interface);
162     auto reply = _bus.call(method);
163 
164     reply.read(properties);
165 
166     return properties;
167 }
168 
169 void DataInterface::getProperty(const std::string& service,
170                                 const std::string& objectPath,
171                                 const std::string& interface,
172                                 const std::string& property,
173                                 DBusValue& value) const
174 {
175 
176     auto method = _bus.new_method_call(service.c_str(), objectPath.c_str(),
177                                        interface::dbusProperty, "Get");
178     method.append(interface, property);
179     auto reply = _bus.call(method);
180 
181     reply.read(value);
182 }
183 
184 DBusPathList DataInterface::getPaths(const DBusInterfaceList& interfaces) const
185 {
186 
187     auto method = _bus.new_method_call(
188         service_name::objectMapper, object_path::objectMapper,
189         interface::objectMapper, "GetSubTreePaths");
190 
191     method.append(std::string{"/"}, 0, interfaces);
192 
193     auto reply = _bus.call(method);
194 
195     DBusPathList paths;
196     reply.read(paths);
197 
198     return paths;
199 }
200 
201 DBusService DataInterface::getService(const std::string& objectPath,
202                                       const std::string& interface) const
203 {
204     auto method = _bus.new_method_call(service_name::objectMapper,
205                                        object_path::objectMapper,
206                                        interface::objectMapper, "GetObject");
207 
208     method.append(objectPath, std::vector<std::string>({interface}));
209 
210     auto reply = _bus.call(method);
211 
212     std::map<DBusService, DBusInterfaceList> response;
213     reply.read(response);
214 
215     if (!response.empty())
216     {
217         return response.begin()->first;
218     }
219 
220     return std::string{};
221 }
222 
223 void DataInterface::readBMCFWVersion()
224 {
225     _bmcFWVersion =
226         phosphor::logging::util::getOSReleaseValue("VERSION").value_or("");
227 }
228 
229 void DataInterface::readServerFWVersion()
230 {
231     auto value =
232         phosphor::logging::util::getOSReleaseValue("VERSION_ID").value_or("");
233     if ((value != "") && (value.find_last_of(')') != std::string::npos))
234     {
235         std::size_t pos = value.find_first_of('(') + 1;
236         _serverFWVersion = value.substr(pos, value.find_last_of(')') - pos);
237     }
238 }
239 
240 void DataInterface::readBMCFWVersionID()
241 {
242     _bmcFWVersionID =
243         phosphor::logging::util::getOSReleaseValue("VERSION_ID").value_or("");
244 }
245 
246 std::string DataInterface::getMachineTypeModel() const
247 {
248     std::string model;
249     try
250     {
251 
252         auto service = getService(object_path::systemInv, interface::invAsset);
253         if (!service.empty())
254         {
255             DBusValue value;
256             getProperty(service, object_path::systemInv, interface::invAsset,
257                         "Model", value);
258 
259             model = std::get<std::string>(value);
260         }
261     }
262     catch (const std::exception& e)
263     {
264         log<level::WARNING>(fmt::format("Failed reading Model property from "
265                                         "Interface: {} exception: {}",
266                                         interface::invAsset, e.what())
267                                 .c_str());
268     }
269 
270     return model;
271 }
272 
273 std::string DataInterface::getMachineSerialNumber() const
274 {
275     std::string sn;
276     try
277     {
278 
279         auto service = getService(object_path::systemInv, interface::invAsset);
280         if (!service.empty())
281         {
282             DBusValue value;
283             getProperty(service, object_path::systemInv, interface::invAsset,
284                         "SerialNumber", value);
285 
286             sn = std::get<std::string>(value);
287         }
288     }
289     catch (const std::exception& e)
290     {
291         log<level::WARNING>(
292             fmt::format("Failed reading SerialNumber property from "
293                         "Interface: {} exception: {}",
294                         interface::invAsset, e.what())
295                 .c_str());
296     }
297 
298     return sn;
299 }
300 
301 std::string DataInterface::getMotherboardCCIN() const
302 {
303     std::string ccin;
304 
305     try
306     {
307         auto service =
308             getService(object_path::motherBoardInv, interface::viniRecordVPD);
309         if (!service.empty())
310         {
311             DBusValue value;
312             getProperty(service, object_path::motherBoardInv,
313                         interface::viniRecordVPD, "CC", value);
314 
315             auto cc = std::get<std::vector<uint8_t>>(value);
316             ccin = std::string{cc.begin(), cc.end()};
317         }
318     }
319     catch (const std::exception& e)
320     {
321         log<level::WARNING>(
322             fmt::format("Failed reading Motherboard CCIN property from "
323                         "Interface: {} exception: {}",
324                         interface::viniRecordVPD, e.what())
325                 .c_str());
326     }
327 
328     return ccin;
329 }
330 
331 std::vector<uint8_t> DataInterface::getSystemIMKeyword() const
332 {
333     std::vector<uint8_t> systemIM;
334 
335     try
336     {
337         auto service =
338             getService(object_path::motherBoardInv, interface::vsbpRecordVPD);
339         if (!service.empty())
340         {
341             DBusValue value;
342             getProperty(service, object_path::motherBoardInv,
343                         interface::vsbpRecordVPD, "IM", value);
344 
345             systemIM = std::get<std::vector<uint8_t>>(value);
346         }
347     }
348     catch (const std::exception& e)
349     {
350         log<level::WARNING>(
351             fmt::format("Failed reading System IM property from "
352                         "Interface: {} exception: {}",
353                         interface::vsbpRecordVPD, e.what())
354                 .c_str());
355     }
356 
357     return systemIM;
358 }
359 
360 void DataInterface::getHWCalloutFields(const std::string& inventoryPath,
361                                        std::string& fruPartNumber,
362                                        std::string& ccin,
363                                        std::string& serialNumber) const
364 {
365     // For now, attempt to get all of the properties directly on the path
366     // passed in.  In the future, may need to make use of an algorithm
367     // to figure out which inventory objects actually hold these
368     // interfaces in the case of non FRUs, or possibly another service
369     // will provide this info.  Any missing interfaces will result
370     // in exceptions being thrown.
371 
372     auto service = getService(inventoryPath, interface::viniRecordVPD);
373 
374     auto properties =
375         getAllProperties(service, inventoryPath, interface::viniRecordVPD);
376 
377     auto value = std::get<std::vector<uint8_t>>(properties["FN"]);
378     fruPartNumber = std::string{value.begin(), value.end()};
379 
380     value = std::get<std::vector<uint8_t>>(properties["CC"]);
381     ccin = std::string{value.begin(), value.end()};
382 
383     value = std::get<std::vector<uint8_t>>(properties["SN"]);
384     serialNumber = std::string{value.begin(), value.end()};
385 }
386 
387 std::string
388     DataInterface::getLocationCode(const std::string& inventoryPath) const
389 {
390     auto service = getService(inventoryPath, interface::locCode);
391 
392     DBusValue locCode;
393     getProperty(service, inventoryPath, interface::locCode, "LocationCode",
394                 locCode);
395 
396     return std::get<std::string>(locCode);
397 }
398 
399 std::string
400     DataInterface::addLocationCodePrefix(const std::string& locationCode)
401 {
402     static const std::string locationCodePrefix{"Ufcs-"};
403 
404     // Technically there are 2 location code prefixes, Ufcs and Umts, so
405     // if it already starts with a U then don't need to do anything.
406     if (locationCode.front() != 'U')
407     {
408         return locationCodePrefix + locationCode;
409     }
410 
411     return locationCode;
412 }
413 
414 std::string DataInterface::expandLocationCode(const std::string& locationCode,
415                                               uint16_t /*node*/) const
416 {
417     auto method =
418         _bus.new_method_call(service_name::vpdManager, object_path::vpdManager,
419                              interface::vpdManager, "GetExpandedLocationCode");
420 
421     method.append(addLocationCodePrefix(locationCode),
422                   static_cast<uint16_t>(0));
423 
424     auto reply = _bus.call(method);
425 
426     std::string expandedLocationCode;
427     reply.read(expandedLocationCode);
428 
429     return expandedLocationCode;
430 }
431 
432 std::string
433     DataInterface::getInventoryFromLocCode(const std::string& locationCode,
434                                            uint16_t node, bool expanded) const
435 {
436     std::string methodName = expanded ? "GetFRUsByExpandedLocationCode"
437                                       : "GetFRUsByUnexpandedLocationCode";
438 
439     auto method =
440         _bus.new_method_call(service_name::vpdManager, object_path::vpdManager,
441                              interface::vpdManager, methodName.c_str());
442 
443     if (expanded)
444     {
445         method.append(locationCode);
446     }
447     else
448     {
449         method.append(addLocationCodePrefix(locationCode), node);
450     }
451 
452     auto reply = _bus.call(method);
453 
454     std::vector<sdbusplus::message::object_path> entries;
455     reply.read(entries);
456 
457     // Get the shortest entry from the paths received, as this
458     // would be the path furthest up the inventory hierarchy so
459     // would be the parent FRU.  There is guaranteed to at least
460     // be one entry if the call didn't fail.
461     std::string shortest{entries[0]};
462 
463     std::for_each(entries.begin(), entries.end(),
464                   [&shortest](const auto& path) {
465                       if (path.str.size() < shortest.size())
466                       {
467                           shortest = path;
468                       }
469                   });
470 
471     return shortest;
472 }
473 
474 void DataInterface::assertLEDGroup(const std::string& ledGroup,
475                                    bool value) const
476 {
477     DBusValue variant = value;
478     auto method =
479         _bus.new_method_call(service_name::ledGroupManager, ledGroup.c_str(),
480                              interface::dbusProperty, "Set");
481     method.append(interface::ledGroup, "Asserted", variant);
482     _bus.call(method);
483 }
484 
485 void DataInterface::setFunctional(const std::string& objectPath,
486                                   bool value) const
487 {
488     DBusValue variant = value;
489     auto service = getService(objectPath, interface::operationalStatus);
490 
491     auto method = _bus.new_method_call(service.c_str(), objectPath.c_str(),
492                                        interface::dbusProperty, "Set");
493 
494     method.append(interface::operationalStatus, "Functional", variant);
495     _bus.call(method);
496 }
497 
498 std::vector<std::string> DataInterface::getSystemNames() const
499 {
500     DBusSubTree subtree;
501     DBusValue names;
502 
503     auto method = _bus.new_method_call(service_name::objectMapper,
504                                        object_path::objectMapper,
505                                        interface::objectMapper, "GetSubTree");
506     method.append(std::string{"/"}, 0,
507                   std::vector<std::string>{interface::compatible});
508     auto reply = _bus.call(method);
509 
510     reply.read(subtree);
511     if (subtree.empty())
512     {
513         throw std::runtime_error("Compatible interface not on D-Bus");
514     }
515 
516     const auto& object = *(subtree.begin());
517     const auto& path = object.first;
518     const auto& service = object.second.begin()->first;
519 
520     getProperty(service, path, interface::compatible, "Names", names);
521 
522     return std::get<std::vector<std::string>>(names);
523 }
524 
525 bool DataInterface::getQuiesceOnError() const
526 {
527     bool ret = false;
528 
529     try
530     {
531         auto service =
532             getService(object_path::logSetting, interface::logSetting);
533         if (!service.empty())
534         {
535             DBusValue value;
536             getProperty(service, object_path::logSetting, interface::logSetting,
537                         "QuiesceOnHwError", value);
538 
539             ret = std::get<bool>(value);
540         }
541     }
542     catch (const std::exception& e)
543     {
544         log<level::WARNING>(
545             fmt::format("Failed reading QuiesceOnHwError property from "
546                         "Interface: {} exception: {}",
547                         interface::logSetting, e.what())
548                 .c_str());
549     }
550 
551     return ret;
552 }
553 
554 } // namespace pels
555 } // namespace openpower
556