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