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