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