EFI_HANDLE *ret_initrd_handle) {
EFI_STATUS err;
- EFI_DEVICE_PATH *dp = (EFI_DEVICE_PATH *) &efi_initrd_device_path;
- EFI_HANDLE handle;
assert(ret_initrd_handle);
if (!iovec_is_set(initrd))
return EFI_SUCCESS;
- /* Check if a LINUX_INITRD_MEDIA_GUID DevicePath is already registered. LocateDevicePath checks for
- * the "closest DevicePath" and returns its handle, whereas InstallMultipleProtocolInterfaces() only
- * matches identical DevicePaths. */
- err = BS->LocateDevicePath(MAKE_GUID_PTR(EFI_LOAD_FILE2_PROTOCOL), &dp, &handle);
- if (err != EFI_NOT_FOUND) /* InitrdMedia is already registered */
- return EFI_ALREADY_STARTED;
+ /* We want to override the LINUX_INITRD_MEDIA device, let's hence first unregister any existing
+ * one. We don't really expect multiple of these to be registered, but who knows? Let's kill all we
+ * can find. */
+ for (unsigned attempt = 0;; attempt++) {
+
+ if (attempt >= 16)
+ return log_debug_status(EFI_DEVICE_ERROR, "Unable to free LINUX_INITRD_MEDIA device path after %u attempts, giving up.", attempt);
+
+ EFI_DEVICE_PATH *dp = (EFI_DEVICE_PATH *) &efi_initrd_device_path;
+ EFI_HANDLE handle = NULL;
+ err = BS->LocateDevicePath(MAKE_GUID_PTR(EFI_LOAD_FILE2_PROTOCOL), &dp, &handle);
+ if (err == EFI_NOT_FOUND) /* Yay! All gone */
+ break;
+ if (err != EFI_SUCCESS)
+ return log_debug_status(err, "Failed to locate LINUX_INITRD_MEDIA device: %m");
+
+ /* Get the *actually* installed pointer for the device path */
+ err = BS->HandleProtocol(handle, MAKE_GUID_PTR(EFI_DEVICE_PATH_PROTOCOL), (void**) &dp);
+ if (err != EFI_SUCCESS)
+ return log_debug_status(err, "Failed to acquire DevicePath protocol on LINUX_INITRD_MEDIA device: %m");
+
+ /* Take away the device path protocol */
+ err = BS->UninstallMultipleProtocolInterfaces(
+ handle,
+ MAKE_GUID_PTR(EFI_DEVICE_PATH_PROTOCOL), dp,
+ /* sentinel= */ NULL);
+ if (err != EFI_SUCCESS)
+ return log_debug_status(err, "Unable to release DevicePath protocol from old handle: %m");
+
+ /* NB: we leave the handle around (and thus leave the LoadFile2 protocol installed), because
+ * the owner might be unhappy if we destroy it for them. It will no longer have the device
+ * path we want to take possession of on it though. The assumption here is that whoever
+ * registered the device path is OK with the device path being taken away, even if it might
+ * not be OK with the handle being invalidated as a whole. */
+
+ log_debug("Successfully unregistered previous LINUX_INITRD_MEDIA device.");
+ }
_cleanup_free_ struct initrd_loader *loader = xnew(struct initrd_loader, 1);
*loader = (struct initrd_loader) {
if (err != EFI_SUCCESS)
return log_debug_status(err, "Failed to acquire LoadFile2 protocol on our own initrd handle: %m");
- /* uninstall all protocols thus destroying the handle */
+ /* We uninstall the DevicePath and the LoadFile2 protocol in separate steps. That's because we want
+ * to gracefully handle the former (because it's OK if something else takes over the device path),
+ * but be strict on the latter, because that's genuinely ours */
+
+ (void) BS->UninstallMultipleProtocolInterfaces(
+ initrd_handle,
+ MAKE_GUID_PTR(EFI_DEVICE_PATH_PROTOCOL), &efi_initrd_device_path,
+ /* sentinel= */ NULL);
+
+ /* This second call will also invalidate the handle, because it should be the last protocol on the handle */
err = BS->UninstallMultipleProtocolInterfaces(
- initrd_handle, MAKE_GUID_PTR(EFI_DEVICE_PATH_PROTOCOL),
- &efi_initrd_device_path, MAKE_GUID_PTR(EFI_LOAD_FILE2_PROTOCOL),
- loader,
- NULL);
+ initrd_handle,
+ MAKE_GUID_PTR(EFI_LOAD_FILE2_PROTOCOL), loader,
+ /* sentinel= */ NULL);
if (err != EFI_SUCCESS)
return log_debug_status(err, "Failed to uninstall LoadFile2 protocol from our own initrd handle: %m");