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