blob: 38ebeca8c44a574e1bd6c8861a90addd3b8de2c2 [file] [log] [blame]
Brad Bishop099543e2020-11-09 15:37:58 -05001// SPDX-License-Identifier: Apache-2.0
2
3/**@file functions.cpp*/
4
Adriana Kobylak56f538c2021-06-16 20:37:21 +00005#include "config.h"
6
Brad Bishop099543e2020-11-09 15:37:58 -05007#include "functions.hpp"
8
Adriana Kobylakae0998f2021-06-16 19:52:24 +00009#include <nlohmann/json.hpp>
Adriana Kobylak56f538c2021-06-16 20:37:21 +000010#include <phosphor-logging/log.hpp>
Brad Bishop099543e2020-11-09 15:37:58 -050011#include <sdbusplus/bus.hpp>
12#include <sdbusplus/bus/match.hpp>
Adriana Kobylakc79fa912021-06-22 15:37:50 +000013#include <sdbusplus/exception.hpp>
Brad Bishop099543e2020-11-09 15:37:58 -050014#include <sdbusplus/message.hpp>
15#include <sdeventplus/event.hpp>
16
17#include <filesystem>
Adriana Kobylakae0998f2021-06-16 19:52:24 +000018#include <fstream>
Brad Bishop099543e2020-11-09 15:37:58 -050019#include <functional>
20#include <iostream>
21#include <map>
22#include <memory>
23#include <string>
24#include <variant>
25#include <vector>
26
27namespace functions
28{
29namespace process_hostfirmware
30{
31
Adriana Kobylak56f538c2021-06-16 20:37:21 +000032using namespace phosphor::logging;
Adriana Kobylakebf67bf2021-06-21 18:38:17 +000033using InterfacesPropertiesMap =
34 std::map<std::string,
35 std::map<std::string, std::variant<std::vector<std::string>>>>;
36using ManagedObjectType =
37 std::map<sdbusplus::message::object_path, InterfacesPropertiesMap>;
Adriana Kobylak56f538c2021-06-16 20:37:21 +000038
Brad Bishop099543e2020-11-09 15:37:58 -050039/**
Adriana Kobylakfd4a6082021-06-21 15:53:34 +000040 * @brief GetObject function to find the service given an object path.
41 * It is used to determine if a service is running, so there is no need
42 * to specify interfaces as a parameter to constrain the search.
43 */
44std::string getObject(sdbusplus::bus::bus& bus, const std::string& path)
45{
46 auto method = bus.new_method_call(MAPPER_BUSNAME, MAPPER_PATH,
47 MAPPER_BUSNAME, "GetObject");
48 method.append(path);
49 std::vector<std::string> interfaces;
50 method.append(interfaces);
51
52 std::vector<std::pair<std::string, std::vector<std::string>>> response;
53
54 try
55 {
56 auto reply = bus.call(method);
57 reply.read(response);
58 if (response.empty())
59 {
60 return std::string{};
61 }
62 }
Patrick Williams7b5685d2021-09-02 09:32:14 -050063 catch (const sdbusplus::exception::exception& e)
Adriana Kobylakfd4a6082021-06-21 15:53:34 +000064 {
65 return std::string{};
66 }
67 return response[0].first;
68}
69
70/**
Adriana Kobylakebf67bf2021-06-21 18:38:17 +000071 * @brief Returns the managed objects for a given service
72 */
73ManagedObjectType getManagedObjects(sdbusplus::bus::bus& bus,
74 const std::string& service)
75{
76 auto method = bus.new_method_call(service.c_str(), "/",
77 "org.freedesktop.DBus.ObjectManager",
78 "GetManagedObjects");
79
80 ManagedObjectType objects;
81
82 try
83 {
84 auto reply = bus.call(method);
85 reply.read(objects);
86 }
Patrick Williams7b5685d2021-09-02 09:32:14 -050087 catch (const sdbusplus::exception::exception& e)
Adriana Kobylakebf67bf2021-06-21 18:38:17 +000088 {
89 return ManagedObjectType{};
90 }
91 return objects;
92}
93
94/**
Brad Bishop099543e2020-11-09 15:37:58 -050095 * @brief Issue callbacks safely
96 *
97 * std::function can be empty, so this wrapper method checks for that prior to
98 * calling it to avoid std::bad_function_call
99 *
100 * @tparam Sig the types of the std::function arguments
101 * @tparam Args the deduced argument types
102 * @param[in] callback the callback being wrapped
103 * @param[in] args the callback arguments
104 */
105template <typename... Sig, typename... Args>
106void makeCallback(const std::function<void(Sig...)>& callback, Args&&... args)
107{
108 if (callback)
109 {
110 callback(std::forward<Args>(args)...);
111 }
112}
113
114/**
115 * @brief Get file extensions for IBMCompatibleSystem
116 *
117 * IBM host firmware can be deployed as blobs (files) in a filesystem. Host
118 * firmware blobs for different values of
119 * xyz.openbmc_project.Configuration.IBMCompatibleSystem are packaged with
120 * different filename extensions. getExtensionsForIbmCompatibleSystem
121 * maintains the mapping from a given value of
122 * xyz.openbmc_project.Configuration.IBMCompatibleSystem to an array of
123 * filename extensions.
124 *
125 * If a mapping is found getExtensionsForIbmCompatibleSystem returns true and
126 * the extensions parameter is reset with the map entry. If no mapping is
127 * found getExtensionsForIbmCompatibleSystem returns false and extensions is
128 * unmodified.
129 *
130 * @param[in] extensionMap a map of
131 * xyz.openbmc_project.Configuration.IBMCompatibleSystem to host firmware blob
132 * file extensions.
133 * @param[in] ibmCompatibleSystem The names property of an instance of
134 * xyz.openbmc_project.Configuration.IBMCompatibleSystem
135 * @param[out] extentions the host firmware blob file extensions
136 * @return true if an entry was found, otherwise false
137 */
138bool getExtensionsForIbmCompatibleSystem(
139 const std::map<std::string, std::vector<std::string>>& extensionMap,
140 const std::vector<std::string>& ibmCompatibleSystem,
141 std::vector<std::string>& extensions)
142{
143 for (const auto& system : ibmCompatibleSystem)
144 {
145 auto extensionMapIterator = extensionMap.find(system);
146 if (extensionMapIterator != extensionMap.end())
147 {
148 extensions = extensionMapIterator->second;
149 return true;
150 }
151 }
152
153 return false;
154}
155
156/**
157 * @brief Write host firmware well-known name
158 *
159 * A wrapper around std::filesystem::create_symlink that avoids EEXIST by
160 * deleting any pre-existing file.
161 *
162 * @param[in] linkTarget The link target argument to
163 * std::filesystem::create_symlink
164 * @param[in] linkPath The link path argument to std::filesystem::create_symlink
165 * @param[in] errorCallback A callback made in the event of filesystem errors.
166 */
167void writeLink(const std::filesystem::path& linkTarget,
168 const std::filesystem::path& linkPath,
169 const ErrorCallbackType& errorCallback)
170{
171 std::error_code ec;
172
173 // remove files with the same name as the symlink to be created,
174 // otherwise symlink will fail with EEXIST.
175 if (!std::filesystem::remove(linkPath, ec))
176 {
177 if (ec)
178 {
179 makeCallback(errorCallback, linkPath, ec);
180 return;
181 }
182 }
183
184 std::filesystem::create_symlink(linkTarget, linkPath, ec);
185 if (ec)
186 {
187 makeCallback(errorCallback, linkPath, ec);
188 return;
189 }
190}
191
192/**
193 * @brief Find host firmware blob files that need well-known names
194 *
195 * The IBM host firmware runtime looks for data and/or additional code while
196 * bootstraping in files with well-known names. findLinks uses the provided
197 * extensions argument to find host firmware blob files that require a
198 * well-known name. When a blob is found, issue the provided callback
199 * (typically a function that will write a symlink).
200 *
201 * @param[in] hostFirmwareDirectory The directory in which findLinks should
202 * look for host firmware blob files that need well-known names.
203 * @param[in] extentions The extensions of the firmware blob files denote a
204 * host firmware blob file requires a well-known name.
205 * @param[in] errorCallback A callback made in the event of filesystem errors.
206 * @param[in] linkCallback A callback made when host firmware blob files
207 * needing a well known name are found.
208 */
209void findLinks(const std::filesystem::path& hostFirmwareDirectory,
210 const std::vector<std::string>& extensions,
211 const ErrorCallbackType& errorCallback,
212 const LinkCallbackType& linkCallback)
213{
214 std::error_code ec;
215 std::filesystem::directory_iterator directoryIterator(hostFirmwareDirectory,
216 ec);
217 if (ec)
218 {
219 makeCallback(errorCallback, hostFirmwareDirectory, ec);
220 return;
221 }
222
Adriana Kobylak4e82bc82021-09-28 19:25:39 +0000223 // Create a symlink for pnor.toc
224 static const auto tocLid = "81e00994.lid";
225 auto tocLidPath = hostFirmwareDirectory / tocLid;
226 if (std::filesystem::exists(tocLidPath))
227 {
228 static const auto tocName = "pnor.toc";
229 auto tocLinkPath = hostFirmwareDirectory / tocName;
230 makeCallback(linkCallback, tocLid, tocLinkPath, errorCallback);
231 }
232
Brad Bishop099543e2020-11-09 15:37:58 -0500233 for (; directoryIterator != std::filesystem::end(directoryIterator);
234 directoryIterator.increment(ec))
235 {
236 const auto& file = directoryIterator->path();
237 if (ec)
238 {
239 makeCallback(errorCallback, file, ec);
240 // quit here if the increment call failed otherwise the loop may
241 // never finish
242 break;
243 }
244
245 if (std::find(extensions.begin(), extensions.end(), file.extension()) ==
246 extensions.end())
247 {
248 // this file doesn't have an extension or doesn't match any of the
249 // provided extensions.
250 continue;
251 }
252
253 auto linkPath(file.parent_path().append(
254 static_cast<const std::string&>(file.stem())));
255
256 makeCallback(linkCallback, file.filename(), linkPath, errorCallback);
257 }
258}
259
260/**
Adriana Kobylakae0998f2021-06-16 19:52:24 +0000261 * @brief Parse the elements json file and construct a string with the data to
262 * be used to update the bios attribute table.
263 *
264 * @param[in] elementsJsonFilePath - The path to the host firmware json file.
265 * @param[in] extensions - The extensions of the firmware blob files.
Adriana Kobylak53a27392021-06-14 17:42:40 +0000266 */
Adriana Kobylakae0998f2021-06-16 19:52:24 +0000267std::string getBiosAttrStr(const std::filesystem::path& elementsJsonFilePath,
268 const std::vector<std::string>& extensions)
Adriana Kobylak56f538c2021-06-16 20:37:21 +0000269{
270 std::string biosAttrStr{};
271
Adriana Kobylakae0998f2021-06-16 19:52:24 +0000272 std::ifstream jsonFile(elementsJsonFilePath.c_str());
273 if (!jsonFile)
274 {
275 return {};
276 }
277
278 std::map<std::string, std::string> attr;
279 auto data = nlohmann::json::parse(jsonFile, nullptr, false);
280 if (data.is_discarded())
281 {
282 log<level::ERR>("Error parsing JSON file",
283 entry("FILE=%s", elementsJsonFilePath.c_str()));
284 return {};
285 }
286
287 // .get requires a non-const iterator
288 for (auto& iter : data["lids"])
289 {
290 std::string name{};
291 std::string lid{};
292
293 try
294 {
295 name = iter["element_name"].get<std::string>();
296 lid = iter["short_lid_name"].get<std::string>();
297 }
Patrick Williams97a709b2021-10-06 13:05:40 -0500298 catch (const std::exception& e)
Adriana Kobylakae0998f2021-06-16 19:52:24 +0000299 {
300 // Possibly the element or lid name field was not found
301 log<level::ERR>("Error reading JSON field",
302 entry("FILE=%s", elementsJsonFilePath.c_str()),
303 entry("ERROR=%s", e.what()));
304 continue;
305 }
306
307 // The elements with the ipl extension have higher priority. Therefore
Adriana Kobylak9cbc06b2021-11-09 22:02:44 +0000308 // Use operator[] to overwrite value if an entry for it already exists,
309 // and create a second entry with key name element_RT to specify it as
310 // a runtime element.
311 // Ex: if the JSON contains an entry A.P10 with lid name X, it'll create
312 // and try A=X. If the JSON also contained an entry A.P10.iplTime with
313 // lid name Y, the A entry would be overwritten to be A=Y and a second
314 // entry A_RT=X would be created.
Adriana Kobylakae0998f2021-06-16 19:52:24 +0000315 constexpr auto iplExtension = ".iplTime";
Adriana Kobylak9cbc06b2021-11-09 22:02:44 +0000316 constexpr auto runtimeSuffix = "_RT";
Adriana Kobylakae0998f2021-06-16 19:52:24 +0000317 std::filesystem::path path(name);
318 if (path.extension() == iplExtension)
319 {
320 // Some elements have an additional extension, ex: .P10.iplTime
321 // Strip off the ipl extension with stem(), then check if there is
322 // an additional extension with extension().
323 if (!path.stem().extension().empty())
324 {
325 // Check if the extension matches the extensions for this system
326 if (std::find(extensions.begin(), extensions.end(),
327 path.stem().extension()) == extensions.end())
328 {
329 continue;
330 }
331 }
332 // Get the element name without extensions by calling stem() twice
333 // since stem() returns the base name if no periods are found.
334 // Therefore both "element.P10" and "element.P10.iplTime" would
335 // become "element".
Adriana Kobylak9cbc06b2021-11-09 22:02:44 +0000336 auto keyName = path.stem().stem();
337 auto attrIt = attr.find(keyName);
338 if (attrIt != attr.end())
339 {
340 // Copy the existing entry to a runtime entry
341 auto runtimeKeyName = keyName.string() + runtimeSuffix;
342 attr.insert({runtimeKeyName, attrIt->second});
343 }
344 // Overwrite the exsiting element with the ipl entry
345 attr[keyName] = lid;
Adriana Kobylakae0998f2021-06-16 19:52:24 +0000346 continue;
347 }
348
349 // Process all other extensions. The extension should match the list of
350 // supported extensions for this system. Use .insert() to only add
351 // entries that do not exist, so to not overwrite the values that may
352 // had been added that had the ipl extension.
353 if (std::find(extensions.begin(), extensions.end(), path.extension()) !=
354 extensions.end())
355 {
Adriana Kobylak9cbc06b2021-11-09 22:02:44 +0000356 auto keyName = path.stem();
357 auto attrIt = attr.find(keyName);
358 if (attrIt != attr.end())
359 {
360 // The existing entry is an ipl entry, therefore create this
361 // entry as a runtime one.
362 auto runtimeKeyName = keyName.string() + runtimeSuffix;
363 attr.insert({runtimeKeyName, lid});
364 }
365 else
366 {
367 attr.insert({path.stem(), lid});
368 }
Adriana Kobylakae0998f2021-06-16 19:52:24 +0000369 }
370 }
371 for (const auto& a : attr)
372 {
373 // Build the bios attribute string with format:
374 // "element1=lid1,element2=lid2,elementN=lidN,"
375 biosAttrStr += a.first + "=" + a.second + ",";
Adriana Kobylak5dc5d6c2021-07-20 20:21:16 +0000376
377 // Create symlinks from the hostfw elements to their corresponding
378 // lid files if they don't exist
379 auto elementFilePath =
380 std::filesystem::path("/media/hostfw/running") / a.first;
381 if (!std::filesystem::exists(elementFilePath))
382 {
383 std::error_code ec;
384 auto lidName = a.second + ".lid";
385 std::filesystem::create_symlink(lidName, elementFilePath, ec);
386 if (ec)
387 {
388 log<level::ERR>("Error creating symlink",
389 entry("TARGET=%s", lidName.c_str()),
390 entry("LINK=%s", elementFilePath.c_str()));
391 }
392 }
Adriana Kobylakae0998f2021-06-16 19:52:24 +0000393 }
394
George Liua38f6e62021-09-20 08:53:00 +0800395 // Delete the last comma of the bios attribute string
396 if (biosAttrStr.back() == ',')
397 {
398 return biosAttrStr.substr(0, biosAttrStr.length() - 1);
399 }
400
Adriana Kobylakae0998f2021-06-16 19:52:24 +0000401 return biosAttrStr;
402}
403
404/**
405 * @brief Set the bios attribute table with details of the host firmware data
406 * for this system.
407 *
408 * @param[in] elementsJsonFilePath - The path to the host firmware json file.
409 * @param[in] extentions - The extensions of the firmware blob files.
410 */
411void setBiosAttr(const std::filesystem::path& elementsJsonFilePath,
412 const std::vector<std::string>& extensions)
413{
414 auto biosAttrStr = getBiosAttrStr(elementsJsonFilePath, extensions);
415
Adriana Kobylak56f538c2021-06-16 20:37:21 +0000416 constexpr auto biosConfigPath = "/xyz/openbmc_project/bios_config/manager";
417 constexpr auto biosConfigIntf = "xyz.openbmc_project.BIOSConfig.Manager";
418 constexpr auto dbusAttrName = "hb_lid_ids";
419 constexpr auto dbusAttrType =
420 "xyz.openbmc_project.BIOSConfig.Manager.AttributeType.String";
421
422 using PendingAttributesType = std::vector<std::pair<
423 std::string, std::tuple<std::string, std::variant<std::string>>>>;
424 PendingAttributesType pendingAttributes;
425 pendingAttributes.emplace_back(std::make_pair(
426 dbusAttrName, std::make_tuple(dbusAttrType, biosAttrStr)));
427
428 auto bus = sdbusplus::bus::new_default();
429 auto method = bus.new_method_call(MAPPER_BUSNAME, MAPPER_PATH,
430 MAPPER_INTERFACE, "GetObject");
431 method.append(biosConfigPath, std::vector<std::string>({biosConfigIntf}));
432 std::vector<std::pair<std::string, std::vector<std::string>>> response;
433 try
434 {
435 auto reply = bus.call(method);
436 reply.read(response);
437 if (response.empty())
438 {
439 log<level::ERR>("Error reading mapper response",
440 entry("PATH=%s", biosConfigPath),
441 entry("INTERFACE=%s", biosConfigIntf));
442 return;
443 }
444 auto method = bus.new_method_call((response.begin()->first).c_str(),
445 biosConfigPath,
446 SYSTEMD_PROPERTY_INTERFACE, "Set");
447 method.append(biosConfigIntf, "PendingAttributes",
448 std::variant<PendingAttributesType>(pendingAttributes));
449 bus.call(method);
450 }
Patrick Williams7b5685d2021-09-02 09:32:14 -0500451 catch (const sdbusplus::exception::exception& e)
Adriana Kobylak56f538c2021-06-16 20:37:21 +0000452 {
453 log<level::ERR>("Error setting the bios attribute",
454 entry("ERROR=%s", e.what()),
455 entry("ATTRIBUTE=%s", dbusAttrName));
456 return;
457 }
458}
Adriana Kobylak53a27392021-06-14 17:42:40 +0000459
460/**
Brad Bishop099543e2020-11-09 15:37:58 -0500461 * @brief Make callbacks on
462 * xyz.openbmc_project.Configuration.IBMCompatibleSystem instances.
463 *
464 * Look for an instance of
465 * xyz.openbmc_project.Configuration.IBMCompatibleSystem in the provided
466 * argument and if found, issue the provided callback.
467 *
468 * @param[in] interfacesAndProperties the interfaces in which to look for an
469 * instance of xyz.openbmc_project.Configuration.IBMCompatibleSystem
470 * @param[in] callback the user callback to make if
471 * xyz.openbmc_project.Configuration.IBMCompatibleSystem is found in
472 * interfacesAndProperties
473 * @return true if interfacesAndProperties contained an instance of
474 * xyz.openbmc_project.Configuration.IBMCompatibleSystem, false otherwise
475 */
476bool maybeCall(const std::map<std::string,
477 std::map<std::string,
478 std::variant<std::vector<std::string>>>>&
479 interfacesAndProperties,
480 const MaybeCallCallbackType& callback)
481{
482 using namespace std::string_literals;
483
484 static const auto interfaceName =
485 "xyz.openbmc_project.Configuration.IBMCompatibleSystem"s;
486 auto interfaceIterator = interfacesAndProperties.find(interfaceName);
487 if (interfaceIterator == interfacesAndProperties.cend())
488 {
489 // IBMCompatibleSystem interface not found, so instruct the caller to
490 // keep waiting or try again later.
491 return false;
492 }
493 auto propertyIterator = interfaceIterator->second.find("Names"s);
494 if (propertyIterator == interfaceIterator->second.cend())
495 {
496 // The interface exists but the property doesn't. This is a bug in the
497 // IBMCompatibleSystem implementation. The caller should not try
498 // again.
499 std::cerr << "Names property not implemented on " << interfaceName
500 << "\n";
501 return true;
502 }
503
504 const auto& ibmCompatibleSystem =
505 std::get<std::vector<std::string>>(propertyIterator->second);
506 if (callback)
507 {
508 callback(ibmCompatibleSystem);
509 }
510
511 // IBMCompatibleSystem found and callback issued.
512 return true;
513}
514
515/**
516 * @brief Make callbacks on
517 * xyz.openbmc_project.Configuration.IBMCompatibleSystem instances.
518 *
519 * Look for an instance of
520 * xyz.openbmc_project.Configuration.IBMCompatibleSystem in the provided
521 * argument and if found, issue the provided callback.
522 *
523 * @param[in] message the DBus message in which to look for an instance of
524 * xyz.openbmc_project.Configuration.IBMCompatibleSystem
525 * @param[in] callback the user callback to make if
526 * xyz.openbmc_project.Configuration.IBMCompatibleSystem is found in
527 * message
528 * @return true if message contained an instance of
529 * xyz.openbmc_project.Configuration.IBMCompatibleSystem, false otherwise
530 */
531bool maybeCallMessage(sdbusplus::message::message& message,
532 const MaybeCallCallbackType& callback)
533{
534 std::map<std::string,
535 std::map<std::string, std::variant<std::vector<std::string>>>>
536 interfacesAndProperties;
537 sdbusplus::message::object_path _;
538 message.read(_, interfacesAndProperties);
539 return maybeCall(interfacesAndProperties, callback);
540}
541
542/**
543 * @brief Determine system support for host firmware well-known names.
544 *
545 * Using the provided extensionMap and
546 * xyz.openbmc_project.Configuration.IBMCompatibleSystem, determine if
547 * well-known names for host firmare blob files are necessary and if so, create
548 * them.
549 *
550 * @param[in] extensionMap a map of
551 * xyz.openbmc_project.Configuration.IBMCompatibleSystem to host firmware blob
552 * file extensions.
553 * @param[in] hostFirmwareDirectory The directory in which findLinks should
554 * look for host firmware blob files that need well-known names.
555 * @param[in] ibmCompatibleSystem The names property of an instance of
556 * xyz.openbmc_project.Configuration.IBMCompatibleSystem
557 * @param[in] errorCallback A callback made in the event of filesystem errors.
558 */
559void maybeMakeLinks(
560 const std::map<std::string, std::vector<std::string>>& extensionMap,
561 const std::filesystem::path& hostFirmwareDirectory,
562 const std::vector<std::string>& ibmCompatibleSystem,
563 const ErrorCallbackType& errorCallback)
564{
565 std::vector<std::string> extensions;
566 if (getExtensionsForIbmCompatibleSystem(extensionMap, ibmCompatibleSystem,
567 extensions))
568 {
569 findLinks(hostFirmwareDirectory, extensions, errorCallback, writeLink);
570 }
571}
572
573/**
Adriana Kobylak53a27392021-06-14 17:42:40 +0000574 * @brief Determine system support for updating the bios attribute table.
575 *
576 * Using the provided extensionMap and
577 * xyz.openbmc_project.Configuration.IBMCompatibleSystem, determine if the bios
578 * attribute table needs to be updated.
579 *
580 * @param[in] extensionMap a map of
581 * xyz.openbmc_project.Configuration.IBMCompatibleSystem to host firmware blob
582 * file extensions.
Adriana Kobylakae0998f2021-06-16 19:52:24 +0000583 * @param[in] elementsJsonFilePath The file path to the json file
Adriana Kobylak53a27392021-06-14 17:42:40 +0000584 * @param[in] ibmCompatibleSystem The names property of an instance of
585 * xyz.openbmc_project.Configuration.IBMCompatibleSystem
586 */
587void maybeSetBiosAttr(
588 const std::map<std::string, std::vector<std::string>>& extensionMap,
Adriana Kobylakae0998f2021-06-16 19:52:24 +0000589 const std::filesystem::path& elementsJsonFilePath,
Adriana Kobylak53a27392021-06-14 17:42:40 +0000590 const std::vector<std::string>& ibmCompatibleSystem)
591{
592 std::vector<std::string> extensions;
593 if (getExtensionsForIbmCompatibleSystem(extensionMap, ibmCompatibleSystem,
594 extensions))
595 {
Adriana Kobylakae0998f2021-06-16 19:52:24 +0000596 setBiosAttr(elementsJsonFilePath, extensions);
Adriana Kobylak53a27392021-06-14 17:42:40 +0000597 }
598}
599
600/**
Brad Bishop099543e2020-11-09 15:37:58 -0500601 * @brief process host firmware
602 *
603 * Allocate a callback context and register for DBus.ObjectManager Interfaces
604 * added signals from entity manager.
605 *
606 * Check the current entity manager object tree for a
607 * xyz.openbmc_project.Configuration.IBMCompatibleSystem instance (entity
608 * manager will be dbus activated if it is not running). If one is found,
609 * determine if symlinks need to be created and create them. Instruct the
610 * program event loop to exit.
611 *
612 * If no instance of xyz.openbmc_project.Configuration.IBMCompatibleSystem is
613 * found return the callback context to main, where the program will sleep
614 * until the callback is invoked one or more times and instructs the program
615 * event loop to exit when
616 * xyz.openbmc_project.Configuration.IBMCompatibleSystem is added.
617 *
618 * @param[in] bus a DBus client connection
619 * @param[in] extensionMap a map of
620 * xyz.openbmc_project.Configuration.IBMCompatibleSystem to host firmware blob
621 * file extensions.
622 * @param[in] hostFirmwareDirectory The directory in which processHostFirmware
623 * should look for blob files.
624 * @param[in] errorCallback A callback made in the event of filesystem errors.
625 * @param[in] loop a program event loop
626 * @return nullptr if an instance of
627 * xyz.openbmc_project.Configuration.IBMCompatibleSystem is found, otherwise a
628 * pointer to an sdbusplus match object.
629 */
630std::shared_ptr<void> processHostFirmware(
631 sdbusplus::bus::bus& bus,
632 std::map<std::string, std::vector<std::string>> extensionMap,
633 std::filesystem::path hostFirmwareDirectory,
634 ErrorCallbackType errorCallback, sdeventplus::Event& loop)
635{
636 // ownership of extensionMap, hostFirmwareDirectory and errorCallback can't
637 // be transfered to the match callback because they are needed in the non
638 // async part of this function below, so they need to be moved to the heap.
639 auto pExtensionMap =
640 std::make_shared<decltype(extensionMap)>(std::move(extensionMap));
641 auto pHostFirmwareDirectory =
642 std::make_shared<decltype(hostFirmwareDirectory)>(
643 std::move(hostFirmwareDirectory));
644 auto pErrorCallback =
645 std::make_shared<decltype(errorCallback)>(std::move(errorCallback));
646
647 // register for a callback in case the IBMCompatibleSystem interface has
648 // not yet been published by entity manager.
649 auto interfacesAddedMatch = std::make_shared<sdbusplus::bus::match::match>(
650 bus,
651 sdbusplus::bus::match::rules::interfacesAdded() +
652 sdbusplus::bus::match::rules::sender(
653 "xyz.openbmc_project.EntityManager"),
654 [pExtensionMap, pHostFirmwareDirectory, pErrorCallback,
655 &loop](auto& message) {
656 // bind the extension map, host firmware directory, and error
657 // callback to the maybeMakeLinks function.
658 auto maybeMakeLinksWithArgsBound =
659 std::bind(maybeMakeLinks, std::cref(*pExtensionMap),
660 std::cref(*pHostFirmwareDirectory),
661 std::placeholders::_1, std::cref(*pErrorCallback));
662
663 // if the InterfacesAdded message contains an an instance of
664 // xyz.openbmc_project.Configuration.IBMCompatibleSystem, check to
665 // see if links are necessary on this system and if so, create
666 // them.
667 if (maybeCallMessage(message, maybeMakeLinksWithArgsBound))
668 {
669 // The IBMCompatibleSystem interface was found and the links
670 // were created if applicable. Instruct the event loop /
671 // subcommand to exit.
672 loop.exit(0);
673 }
674 });
675
676 // now that we'll get a callback in the event of an InterfacesAdded signal
677 // (potentially containing
678 // xyz.openbmc_project.Configuration.IBMCompatibleSystem), activate entity
679 // manager if it isn't running and enumerate its objects
680 auto getManagedObjects = bus.new_method_call(
681 "xyz.openbmc_project.EntityManager", "/",
682 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
Brad Bishop099543e2020-11-09 15:37:58 -0500683 std::map<std::string,
684 std::map<std::string, std::variant<std::vector<std::string>>>>
685 interfacesAndProperties;
686 std::map<sdbusplus::message::object_path, decltype(interfacesAndProperties)>
687 objects;
Adriana Kobylakc79fa912021-06-22 15:37:50 +0000688 try
689 {
690 auto reply = bus.call(getManagedObjects);
691 reply.read(objects);
692 }
Patrick Williams7b5685d2021-09-02 09:32:14 -0500693 catch (const sdbusplus::exception::exception& e)
Adriana Kobylakc79fa912021-06-22 15:37:50 +0000694 {
695 // Error querying the EntityManager interface. Return the match to have
696 // the callback run if/when the interface appears in D-Bus.
697 return interfacesAddedMatch;
698 }
Brad Bishop099543e2020-11-09 15:37:58 -0500699
700 // bind the extension map, host firmware directory, and error callback to
701 // the maybeMakeLinks function.
702 auto maybeMakeLinksWithArgsBound =
703 std::bind(maybeMakeLinks, std::cref(*pExtensionMap),
704 std::cref(*pHostFirmwareDirectory), std::placeholders::_1,
705 std::cref(*pErrorCallback));
706
707 for (const auto& pair : objects)
708 {
709 std::tie(std::ignore, interfacesAndProperties) = pair;
710 // if interfacesAndProperties contains an an instance of
711 // xyz.openbmc_project.Configuration.IBMCompatibleSystem, check to see
712 // if links are necessary on this system and if so, create them
713 if (maybeCall(interfacesAndProperties, maybeMakeLinksWithArgsBound))
714 {
715 // The IBMCompatibleSystem interface is already on the bus and the
716 // links were created if applicable. Instruct the event loop to
717 // exit.
718 loop.exit(0);
719 // The match object isn't needed anymore, so destroy it on return.
720 return nullptr;
721 }
722 }
723
724 // The IBMCompatibleSystem interface has not yet been published. Move
725 // ownership of the match callback to the caller.
726 return interfacesAddedMatch;
727}
Adriana Kobylak53a27392021-06-14 17:42:40 +0000728
729/**
730 * @brief Update the Bios Attribute Table
731 *
732 * If an instance of xyz.openbmc_project.Configuration.IBMCompatibleSystem is
733 * found, update the Bios Attribute Table with the appropriate host firmware
734 * data.
735 *
736 * @param[in] bus - D-Bus client connection.
737 * @param[in] extensionMap - Map of IBMCompatibleSystem names and host firmware
738 * file extensions.
Adriana Kobylakae0998f2021-06-16 19:52:24 +0000739 * @param[in] elementsJsonFilePath - The Path to the json file
Adriana Kobylak53a27392021-06-14 17:42:40 +0000740 * @param[in] loop - Program event loop.
741 * @return nullptr
742 */
Adriana Kobylakebf67bf2021-06-21 18:38:17 +0000743std::vector<std::shared_ptr<void>> updateBiosAttrTable(
Adriana Kobylak53a27392021-06-14 17:42:40 +0000744 sdbusplus::bus::bus& bus,
745 std::map<std::string, std::vector<std::string>> extensionMap,
Adriana Kobylakae0998f2021-06-16 19:52:24 +0000746 std::filesystem::path elementsJsonFilePath, sdeventplus::Event& loop)
Adriana Kobylak53a27392021-06-14 17:42:40 +0000747{
Adriana Kobylakd0379ea2021-06-21 16:15:59 +0000748 constexpr auto pldmPath = "/xyz/openbmc_project/pldm";
Adriana Kobylakebf67bf2021-06-21 18:38:17 +0000749 constexpr auto entityManagerServiceName =
750 "xyz.openbmc_project.EntityManager";
Adriana Kobylakd0379ea2021-06-21 16:15:59 +0000751
Adriana Kobylak53a27392021-06-14 17:42:40 +0000752 auto pExtensionMap =
753 std::make_shared<decltype(extensionMap)>(std::move(extensionMap));
Adriana Kobylakae0998f2021-06-16 19:52:24 +0000754 auto pElementsJsonFilePath =
755 std::make_shared<decltype(elementsJsonFilePath)>(
756 std::move(elementsJsonFilePath));
Adriana Kobylak53a27392021-06-14 17:42:40 +0000757
Adriana Kobylakd0379ea2021-06-21 16:15:59 +0000758 // Entity Manager is needed to get the list of supported extensions. Add a
759 // match to monitor interfaces added in case it's not running yet.
760 auto maybeSetAttrWithArgsBound =
761 std::bind(maybeSetBiosAttr, std::cref(*pExtensionMap),
762 std::cref(*pElementsJsonFilePath), std::placeholders::_1);
763
Adriana Kobylakebf67bf2021-06-21 18:38:17 +0000764 std::vector<std::shared_ptr<void>> matches;
765 matches.emplace_back(std::make_shared<sdbusplus::bus::match::match>(
Adriana Kobylakd0379ea2021-06-21 16:15:59 +0000766 bus,
767 sdbusplus::bus::match::rules::interfacesAdded() +
768 sdbusplus::bus::match::rules::sender(
769 "xyz.openbmc_project.EntityManager"),
770 [pldmPath, pExtensionMap, pElementsJsonFilePath,
771 maybeSetAttrWithArgsBound, &loop](auto& message) {
772 auto bus = sdbusplus::bus::new_default();
773 auto pldmObject = getObject(bus, pldmPath);
774 if (pldmObject.empty())
775 {
776 return;
777 }
778 if (maybeCallMessage(message, maybeSetAttrWithArgsBound))
779 {
780 loop.exit(0);
781 }
Adriana Kobylakebf67bf2021-06-21 18:38:17 +0000782 }));
783 matches.emplace_back(std::make_shared<sdbusplus::bus::match::match>(
784 bus,
785 sdbusplus::bus::match::rules::nameOwnerChanged() +
786 sdbusplus::bus::match::rules::arg0namespace(
787 "xyz.openbmc_project.PLDM"),
788 [pExtensionMap, pElementsJsonFilePath, maybeSetAttrWithArgsBound,
789 &loop](auto& message) {
790 std::string name;
791 std::string oldOwner;
792 std::string newOwner;
793 message.read(name, oldOwner, newOwner);
794
795 if (newOwner.empty())
796 {
797 return;
798 }
799
800 auto bus = sdbusplus::bus::new_default();
801 InterfacesPropertiesMap interfacesAndProperties;
802 auto objects = getManagedObjects(bus, entityManagerServiceName);
803 for (const auto& pair : objects)
804 {
805 std::tie(std::ignore, interfacesAndProperties) = pair;
806 if (maybeCall(interfacesAndProperties,
807 maybeSetAttrWithArgsBound))
808 {
809 loop.exit(0);
810 }
811 }
812 }));
Adriana Kobylakd0379ea2021-06-21 16:15:59 +0000813
Adriana Kobylakfd4a6082021-06-21 15:53:34 +0000814 // The BIOS attribute table can only be updated if PLDM is running because
815 // PLDM is the one that exposes this property. Return if it's not running.
Adriana Kobylakfd4a6082021-06-21 15:53:34 +0000816 auto pldmObject = getObject(bus, pldmPath);
817 if (pldmObject.empty())
818 {
Adriana Kobylakebf67bf2021-06-21 18:38:17 +0000819 return matches;
Adriana Kobylakfd4a6082021-06-21 15:53:34 +0000820 }
821
Adriana Kobylakebf67bf2021-06-21 18:38:17 +0000822 InterfacesPropertiesMap interfacesAndProperties;
823 auto objects = getManagedObjects(bus, entityManagerServiceName);
Adriana Kobylak53a27392021-06-14 17:42:40 +0000824 for (const auto& pair : objects)
825 {
826 std::tie(std::ignore, interfacesAndProperties) = pair;
827 if (maybeCall(interfacesAndProperties, maybeSetAttrWithArgsBound))
828 {
Adriana Kobylakd0379ea2021-06-21 16:15:59 +0000829 loop.exit(0);
Adriana Kobylakebf67bf2021-06-21 18:38:17 +0000830 return {};
Adriana Kobylak53a27392021-06-14 17:42:40 +0000831 }
832 }
833
Adriana Kobylakebf67bf2021-06-21 18:38:17 +0000834 return matches;
Adriana Kobylak53a27392021-06-14 17:42:40 +0000835}
836
Brad Bishop099543e2020-11-09 15:37:58 -0500837} // namespace process_hostfirmware
838} // namespace functions