blob: 2b6fbad583e0ca6dbb4f0f7f42bfdee6413dbde2 [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 Kobylak56f538c2021-06-16 20:37:21 +00009#include <phosphor-logging/log.hpp>
Brad Bishop099543e2020-11-09 15:37:58 -050010#include <sdbusplus/bus.hpp>
11#include <sdbusplus/bus/match.hpp>
Adriana Kobylakc79fa912021-06-22 15:37:50 +000012#include <sdbusplus/exception.hpp>
Brad Bishop099543e2020-11-09 15:37:58 -050013#include <sdbusplus/message.hpp>
14#include <sdeventplus/event.hpp>
15
16#include <filesystem>
17#include <functional>
18#include <iostream>
19#include <map>
20#include <memory>
21#include <string>
22#include <variant>
23#include <vector>
24
25namespace functions
26{
27namespace process_hostfirmware
28{
29
Adriana Kobylak56f538c2021-06-16 20:37:21 +000030using namespace phosphor::logging;
31
Brad Bishop099543e2020-11-09 15:37:58 -050032/**
33 * @brief Issue callbacks safely
34 *
35 * std::function can be empty, so this wrapper method checks for that prior to
36 * calling it to avoid std::bad_function_call
37 *
38 * @tparam Sig the types of the std::function arguments
39 * @tparam Args the deduced argument types
40 * @param[in] callback the callback being wrapped
41 * @param[in] args the callback arguments
42 */
43template <typename... Sig, typename... Args>
44void makeCallback(const std::function<void(Sig...)>& callback, Args&&... args)
45{
46 if (callback)
47 {
48 callback(std::forward<Args>(args)...);
49 }
50}
51
52/**
53 * @brief Get file extensions for IBMCompatibleSystem
54 *
55 * IBM host firmware can be deployed as blobs (files) in a filesystem. Host
56 * firmware blobs for different values of
57 * xyz.openbmc_project.Configuration.IBMCompatibleSystem are packaged with
58 * different filename extensions. getExtensionsForIbmCompatibleSystem
59 * maintains the mapping from a given value of
60 * xyz.openbmc_project.Configuration.IBMCompatibleSystem to an array of
61 * filename extensions.
62 *
63 * If a mapping is found getExtensionsForIbmCompatibleSystem returns true and
64 * the extensions parameter is reset with the map entry. If no mapping is
65 * found getExtensionsForIbmCompatibleSystem returns false and extensions is
66 * unmodified.
67 *
68 * @param[in] extensionMap a map of
69 * xyz.openbmc_project.Configuration.IBMCompatibleSystem to host firmware blob
70 * file extensions.
71 * @param[in] ibmCompatibleSystem The names property of an instance of
72 * xyz.openbmc_project.Configuration.IBMCompatibleSystem
73 * @param[out] extentions the host firmware blob file extensions
74 * @return true if an entry was found, otherwise false
75 */
76bool getExtensionsForIbmCompatibleSystem(
77 const std::map<std::string, std::vector<std::string>>& extensionMap,
78 const std::vector<std::string>& ibmCompatibleSystem,
79 std::vector<std::string>& extensions)
80{
81 for (const auto& system : ibmCompatibleSystem)
82 {
83 auto extensionMapIterator = extensionMap.find(system);
84 if (extensionMapIterator != extensionMap.end())
85 {
86 extensions = extensionMapIterator->second;
87 return true;
88 }
89 }
90
91 return false;
92}
93
94/**
95 * @brief Write host firmware well-known name
96 *
97 * A wrapper around std::filesystem::create_symlink that avoids EEXIST by
98 * deleting any pre-existing file.
99 *
100 * @param[in] linkTarget The link target argument to
101 * std::filesystem::create_symlink
102 * @param[in] linkPath The link path argument to std::filesystem::create_symlink
103 * @param[in] errorCallback A callback made in the event of filesystem errors.
104 */
105void writeLink(const std::filesystem::path& linkTarget,
106 const std::filesystem::path& linkPath,
107 const ErrorCallbackType& errorCallback)
108{
109 std::error_code ec;
110
111 // remove files with the same name as the symlink to be created,
112 // otherwise symlink will fail with EEXIST.
113 if (!std::filesystem::remove(linkPath, ec))
114 {
115 if (ec)
116 {
117 makeCallback(errorCallback, linkPath, ec);
118 return;
119 }
120 }
121
122 std::filesystem::create_symlink(linkTarget, linkPath, ec);
123 if (ec)
124 {
125 makeCallback(errorCallback, linkPath, ec);
126 return;
127 }
128}
129
130/**
131 * @brief Find host firmware blob files that need well-known names
132 *
133 * The IBM host firmware runtime looks for data and/or additional code while
134 * bootstraping in files with well-known names. findLinks uses the provided
135 * extensions argument to find host firmware blob files that require a
136 * well-known name. When a blob is found, issue the provided callback
137 * (typically a function that will write a symlink).
138 *
139 * @param[in] hostFirmwareDirectory The directory in which findLinks should
140 * look for host firmware blob files that need well-known names.
141 * @param[in] extentions The extensions of the firmware blob files denote a
142 * host firmware blob file requires a well-known name.
143 * @param[in] errorCallback A callback made in the event of filesystem errors.
144 * @param[in] linkCallback A callback made when host firmware blob files
145 * needing a well known name are found.
146 */
147void findLinks(const std::filesystem::path& hostFirmwareDirectory,
148 const std::vector<std::string>& extensions,
149 const ErrorCallbackType& errorCallback,
150 const LinkCallbackType& linkCallback)
151{
152 std::error_code ec;
153 std::filesystem::directory_iterator directoryIterator(hostFirmwareDirectory,
154 ec);
155 if (ec)
156 {
157 makeCallback(errorCallback, hostFirmwareDirectory, ec);
158 return;
159 }
160
Adriana Kobylakfdc91fa2021-05-20 16:00:45 +0000161 // Create a symlink from HBB to the corresponding LID file if it exists
162 static const auto hbbLid = "81e0065a.lid";
163 auto hbbLidPath = hostFirmwareDirectory / hbbLid;
164 if (std::filesystem::exists(hbbLidPath))
165 {
166 static const auto hbbName = "HBB";
167 auto hbbLinkPath = hostFirmwareDirectory / hbbName;
168 makeCallback(linkCallback, hbbLid, hbbLinkPath, errorCallback);
169 }
170
Brad Bishop099543e2020-11-09 15:37:58 -0500171 for (; directoryIterator != std::filesystem::end(directoryIterator);
172 directoryIterator.increment(ec))
173 {
174 const auto& file = directoryIterator->path();
175 if (ec)
176 {
177 makeCallback(errorCallback, file, ec);
178 // quit here if the increment call failed otherwise the loop may
179 // never finish
180 break;
181 }
182
183 if (std::find(extensions.begin(), extensions.end(), file.extension()) ==
184 extensions.end())
185 {
186 // this file doesn't have an extension or doesn't match any of the
187 // provided extensions.
188 continue;
189 }
190
191 auto linkPath(file.parent_path().append(
192 static_cast<const std::string&>(file.stem())));
193
194 makeCallback(linkCallback, file.filename(), linkPath, errorCallback);
195 }
196}
197
198/**
Adriana Kobylak53a27392021-06-14 17:42:40 +0000199 * @brief Set the bios attribute table with details of the host firmware data
200 * for this system.
201 */
202void setBiosAttr()
Adriana Kobylak56f538c2021-06-16 20:37:21 +0000203{
204 std::string biosAttrStr{};
205
206 constexpr auto biosConfigPath = "/xyz/openbmc_project/bios_config/manager";
207 constexpr auto biosConfigIntf = "xyz.openbmc_project.BIOSConfig.Manager";
208 constexpr auto dbusAttrName = "hb_lid_ids";
209 constexpr auto dbusAttrType =
210 "xyz.openbmc_project.BIOSConfig.Manager.AttributeType.String";
211
212 using PendingAttributesType = std::vector<std::pair<
213 std::string, std::tuple<std::string, std::variant<std::string>>>>;
214 PendingAttributesType pendingAttributes;
215 pendingAttributes.emplace_back(std::make_pair(
216 dbusAttrName, std::make_tuple(dbusAttrType, biosAttrStr)));
217
218 auto bus = sdbusplus::bus::new_default();
219 auto method = bus.new_method_call(MAPPER_BUSNAME, MAPPER_PATH,
220 MAPPER_INTERFACE, "GetObject");
221 method.append(biosConfigPath, std::vector<std::string>({biosConfigIntf}));
222 std::vector<std::pair<std::string, std::vector<std::string>>> response;
223 try
224 {
225 auto reply = bus.call(method);
226 reply.read(response);
227 if (response.empty())
228 {
229 log<level::ERR>("Error reading mapper response",
230 entry("PATH=%s", biosConfigPath),
231 entry("INTERFACE=%s", biosConfigIntf));
232 return;
233 }
234 auto method = bus.new_method_call((response.begin()->first).c_str(),
235 biosConfigPath,
236 SYSTEMD_PROPERTY_INTERFACE, "Set");
237 method.append(biosConfigIntf, "PendingAttributes",
238 std::variant<PendingAttributesType>(pendingAttributes));
239 bus.call(method);
240 }
241 catch (const sdbusplus::exception::SdBusError& e)
242 {
243 log<level::ERR>("Error setting the bios attribute",
244 entry("ERROR=%s", e.what()),
245 entry("ATTRIBUTE=%s", dbusAttrName));
246 return;
247 }
248}
Adriana Kobylak53a27392021-06-14 17:42:40 +0000249
250/**
Brad Bishop099543e2020-11-09 15:37:58 -0500251 * @brief Make callbacks on
252 * xyz.openbmc_project.Configuration.IBMCompatibleSystem instances.
253 *
254 * Look for an instance of
255 * xyz.openbmc_project.Configuration.IBMCompatibleSystem in the provided
256 * argument and if found, issue the provided callback.
257 *
258 * @param[in] interfacesAndProperties the interfaces in which to look for an
259 * instance of xyz.openbmc_project.Configuration.IBMCompatibleSystem
260 * @param[in] callback the user callback to make if
261 * xyz.openbmc_project.Configuration.IBMCompatibleSystem is found in
262 * interfacesAndProperties
263 * @return true if interfacesAndProperties contained an instance of
264 * xyz.openbmc_project.Configuration.IBMCompatibleSystem, false otherwise
265 */
266bool maybeCall(const std::map<std::string,
267 std::map<std::string,
268 std::variant<std::vector<std::string>>>>&
269 interfacesAndProperties,
270 const MaybeCallCallbackType& callback)
271{
272 using namespace std::string_literals;
273
274 static const auto interfaceName =
275 "xyz.openbmc_project.Configuration.IBMCompatibleSystem"s;
276 auto interfaceIterator = interfacesAndProperties.find(interfaceName);
277 if (interfaceIterator == interfacesAndProperties.cend())
278 {
279 // IBMCompatibleSystem interface not found, so instruct the caller to
280 // keep waiting or try again later.
281 return false;
282 }
283 auto propertyIterator = interfaceIterator->second.find("Names"s);
284 if (propertyIterator == interfaceIterator->second.cend())
285 {
286 // The interface exists but the property doesn't. This is a bug in the
287 // IBMCompatibleSystem implementation. The caller should not try
288 // again.
289 std::cerr << "Names property not implemented on " << interfaceName
290 << "\n";
291 return true;
292 }
293
294 const auto& ibmCompatibleSystem =
295 std::get<std::vector<std::string>>(propertyIterator->second);
296 if (callback)
297 {
298 callback(ibmCompatibleSystem);
299 }
300
301 // IBMCompatibleSystem found and callback issued.
302 return true;
303}
304
305/**
306 * @brief Make callbacks on
307 * xyz.openbmc_project.Configuration.IBMCompatibleSystem instances.
308 *
309 * Look for an instance of
310 * xyz.openbmc_project.Configuration.IBMCompatibleSystem in the provided
311 * argument and if found, issue the provided callback.
312 *
313 * @param[in] message the DBus message in which to look for an instance of
314 * xyz.openbmc_project.Configuration.IBMCompatibleSystem
315 * @param[in] callback the user callback to make if
316 * xyz.openbmc_project.Configuration.IBMCompatibleSystem is found in
317 * message
318 * @return true if message contained an instance of
319 * xyz.openbmc_project.Configuration.IBMCompatibleSystem, false otherwise
320 */
321bool maybeCallMessage(sdbusplus::message::message& message,
322 const MaybeCallCallbackType& callback)
323{
324 std::map<std::string,
325 std::map<std::string, std::variant<std::vector<std::string>>>>
326 interfacesAndProperties;
327 sdbusplus::message::object_path _;
328 message.read(_, interfacesAndProperties);
329 return maybeCall(interfacesAndProperties, callback);
330}
331
332/**
333 * @brief Determine system support for host firmware well-known names.
334 *
335 * Using the provided extensionMap and
336 * xyz.openbmc_project.Configuration.IBMCompatibleSystem, determine if
337 * well-known names for host firmare blob files are necessary and if so, create
338 * them.
339 *
340 * @param[in] extensionMap a map of
341 * xyz.openbmc_project.Configuration.IBMCompatibleSystem to host firmware blob
342 * file extensions.
343 * @param[in] hostFirmwareDirectory The directory in which findLinks should
344 * look for host firmware blob files that need well-known names.
345 * @param[in] ibmCompatibleSystem The names property of an instance of
346 * xyz.openbmc_project.Configuration.IBMCompatibleSystem
347 * @param[in] errorCallback A callback made in the event of filesystem errors.
348 */
349void maybeMakeLinks(
350 const std::map<std::string, std::vector<std::string>>& extensionMap,
351 const std::filesystem::path& hostFirmwareDirectory,
352 const std::vector<std::string>& ibmCompatibleSystem,
353 const ErrorCallbackType& errorCallback)
354{
355 std::vector<std::string> extensions;
356 if (getExtensionsForIbmCompatibleSystem(extensionMap, ibmCompatibleSystem,
357 extensions))
358 {
359 findLinks(hostFirmwareDirectory, extensions, errorCallback, writeLink);
360 }
361}
362
363/**
Adriana Kobylak53a27392021-06-14 17:42:40 +0000364 * @brief Determine system support for updating the bios attribute table.
365 *
366 * Using the provided extensionMap and
367 * xyz.openbmc_project.Configuration.IBMCompatibleSystem, determine if the bios
368 * attribute table needs to be updated.
369 *
370 * @param[in] extensionMap a map of
371 * xyz.openbmc_project.Configuration.IBMCompatibleSystem to host firmware blob
372 * file extensions.
373 * @param[in] ibmCompatibleSystem The names property of an instance of
374 * xyz.openbmc_project.Configuration.IBMCompatibleSystem
375 */
376void maybeSetBiosAttr(
377 const std::map<std::string, std::vector<std::string>>& extensionMap,
378 const std::vector<std::string>& ibmCompatibleSystem)
379{
380 std::vector<std::string> extensions;
381 if (getExtensionsForIbmCompatibleSystem(extensionMap, ibmCompatibleSystem,
382 extensions))
383 {
384 setBiosAttr();
385 }
386}
387
388/**
Brad Bishop099543e2020-11-09 15:37:58 -0500389 * @brief process host firmware
390 *
391 * Allocate a callback context and register for DBus.ObjectManager Interfaces
392 * added signals from entity manager.
393 *
394 * Check the current entity manager object tree for a
395 * xyz.openbmc_project.Configuration.IBMCompatibleSystem instance (entity
396 * manager will be dbus activated if it is not running). If one is found,
397 * determine if symlinks need to be created and create them. Instruct the
398 * program event loop to exit.
399 *
400 * If no instance of xyz.openbmc_project.Configuration.IBMCompatibleSystem is
401 * found return the callback context to main, where the program will sleep
402 * until the callback is invoked one or more times and instructs the program
403 * event loop to exit when
404 * xyz.openbmc_project.Configuration.IBMCompatibleSystem is added.
405 *
406 * @param[in] bus a DBus client connection
407 * @param[in] extensionMap a map of
408 * xyz.openbmc_project.Configuration.IBMCompatibleSystem to host firmware blob
409 * file extensions.
410 * @param[in] hostFirmwareDirectory The directory in which processHostFirmware
411 * should look for blob files.
412 * @param[in] errorCallback A callback made in the event of filesystem errors.
413 * @param[in] loop a program event loop
414 * @return nullptr if an instance of
415 * xyz.openbmc_project.Configuration.IBMCompatibleSystem is found, otherwise a
416 * pointer to an sdbusplus match object.
417 */
418std::shared_ptr<void> processHostFirmware(
419 sdbusplus::bus::bus& bus,
420 std::map<std::string, std::vector<std::string>> extensionMap,
421 std::filesystem::path hostFirmwareDirectory,
422 ErrorCallbackType errorCallback, sdeventplus::Event& loop)
423{
424 // ownership of extensionMap, hostFirmwareDirectory and errorCallback can't
425 // be transfered to the match callback because they are needed in the non
426 // async part of this function below, so they need to be moved to the heap.
427 auto pExtensionMap =
428 std::make_shared<decltype(extensionMap)>(std::move(extensionMap));
429 auto pHostFirmwareDirectory =
430 std::make_shared<decltype(hostFirmwareDirectory)>(
431 std::move(hostFirmwareDirectory));
432 auto pErrorCallback =
433 std::make_shared<decltype(errorCallback)>(std::move(errorCallback));
434
435 // register for a callback in case the IBMCompatibleSystem interface has
436 // not yet been published by entity manager.
437 auto interfacesAddedMatch = std::make_shared<sdbusplus::bus::match::match>(
438 bus,
439 sdbusplus::bus::match::rules::interfacesAdded() +
440 sdbusplus::bus::match::rules::sender(
441 "xyz.openbmc_project.EntityManager"),
442 [pExtensionMap, pHostFirmwareDirectory, pErrorCallback,
443 &loop](auto& message) {
444 // bind the extension map, host firmware directory, and error
445 // callback to the maybeMakeLinks function.
446 auto maybeMakeLinksWithArgsBound =
447 std::bind(maybeMakeLinks, std::cref(*pExtensionMap),
448 std::cref(*pHostFirmwareDirectory),
449 std::placeholders::_1, std::cref(*pErrorCallback));
450
451 // if the InterfacesAdded message contains an an instance of
452 // xyz.openbmc_project.Configuration.IBMCompatibleSystem, check to
453 // see if links are necessary on this system and if so, create
454 // them.
455 if (maybeCallMessage(message, maybeMakeLinksWithArgsBound))
456 {
457 // The IBMCompatibleSystem interface was found and the links
458 // were created if applicable. Instruct the event loop /
459 // subcommand to exit.
460 loop.exit(0);
461 }
462 });
463
464 // now that we'll get a callback in the event of an InterfacesAdded signal
465 // (potentially containing
466 // xyz.openbmc_project.Configuration.IBMCompatibleSystem), activate entity
467 // manager if it isn't running and enumerate its objects
468 auto getManagedObjects = bus.new_method_call(
469 "xyz.openbmc_project.EntityManager", "/",
470 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
Brad Bishop099543e2020-11-09 15:37:58 -0500471 std::map<std::string,
472 std::map<std::string, std::variant<std::vector<std::string>>>>
473 interfacesAndProperties;
474 std::map<sdbusplus::message::object_path, decltype(interfacesAndProperties)>
475 objects;
Adriana Kobylakc79fa912021-06-22 15:37:50 +0000476 try
477 {
478 auto reply = bus.call(getManagedObjects);
479 reply.read(objects);
480 }
481 catch (const sdbusplus::exception::SdBusError& e)
482 {
483 // Error querying the EntityManager interface. Return the match to have
484 // the callback run if/when the interface appears in D-Bus.
485 return interfacesAddedMatch;
486 }
Brad Bishop099543e2020-11-09 15:37:58 -0500487
488 // bind the extension map, host firmware directory, and error callback to
489 // the maybeMakeLinks function.
490 auto maybeMakeLinksWithArgsBound =
491 std::bind(maybeMakeLinks, std::cref(*pExtensionMap),
492 std::cref(*pHostFirmwareDirectory), std::placeholders::_1,
493 std::cref(*pErrorCallback));
494
495 for (const auto& pair : objects)
496 {
497 std::tie(std::ignore, interfacesAndProperties) = pair;
498 // if interfacesAndProperties contains an an instance of
499 // xyz.openbmc_project.Configuration.IBMCompatibleSystem, check to see
500 // if links are necessary on this system and if so, create them
501 if (maybeCall(interfacesAndProperties, maybeMakeLinksWithArgsBound))
502 {
503 // The IBMCompatibleSystem interface is already on the bus and the
504 // links were created if applicable. Instruct the event loop to
505 // exit.
506 loop.exit(0);
507 // The match object isn't needed anymore, so destroy it on return.
508 return nullptr;
509 }
510 }
511
512 // The IBMCompatibleSystem interface has not yet been published. Move
513 // ownership of the match callback to the caller.
514 return interfacesAddedMatch;
515}
Adriana Kobylak53a27392021-06-14 17:42:40 +0000516
517/**
518 * @brief Update the Bios Attribute Table
519 *
520 * If an instance of xyz.openbmc_project.Configuration.IBMCompatibleSystem is
521 * found, update the Bios Attribute Table with the appropriate host firmware
522 * data.
523 *
524 * @param[in] bus - D-Bus client connection.
525 * @param[in] extensionMap - Map of IBMCompatibleSystem names and host firmware
526 * file extensions.
527 * @param[in] loop - Program event loop.
528 * @return nullptr
529 */
530std::shared_ptr<void> updateBiosAttrTable(
531 sdbusplus::bus::bus& bus,
532 std::map<std::string, std::vector<std::string>> extensionMap,
533 sdeventplus::Event& loop)
534{
535 auto pExtensionMap =
536 std::make_shared<decltype(extensionMap)>(std::move(extensionMap));
537
538 auto getManagedObjects = bus.new_method_call(
539 "xyz.openbmc_project.EntityManager", "/",
540 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
541 std::map<std::string,
542 std::map<std::string, std::variant<std::vector<std::string>>>>
543 interfacesAndProperties;
544 std::map<sdbusplus::message::object_path, decltype(interfacesAndProperties)>
545 objects;
546 try
547 {
548 auto reply = bus.call(getManagedObjects);
549 reply.read(objects);
550 }
551 catch (const sdbusplus::exception::SdBusError& e)
552 {}
553
554 auto maybeSetAttrWithArgsBound = std::bind(
555 maybeSetBiosAttr, std::cref(*pExtensionMap), std::placeholders::_1);
556
557 for (const auto& pair : objects)
558 {
559 std::tie(std::ignore, interfacesAndProperties) = pair;
560 if (maybeCall(interfacesAndProperties, maybeSetAttrWithArgsBound))
561 {
562 break;
563 }
564 }
565
566 loop.exit(0);
567 return nullptr;
568}
569
Brad Bishop099543e2020-11-09 15:37:58 -0500570} // namespace process_hostfirmware
571} // namespace functions