1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3 #include <efi.h>
4 #include <efilib.h>
5
6 #include "initrd.h"
7 #include "macro-fundamental.h"
8 #include "missing_efi.h"
9 #include "util.h"
10
11 /* extend LoadFileProtocol */
12 struct initrd_loader {
13 EFI_LOAD_FILE_PROTOCOL load_file;
14 const void *address;
15 UINTN length;
16 };
17
18 /* static structure for LINUX_INITRD_MEDIA device path
19 see https://github.com/torvalds/linux/blob/v5.13/drivers/firmware/efi/libstub/efi-stub-helper.c
20 */
21 static const struct {
22 VENDOR_DEVICE_PATH vendor;
23 EFI_DEVICE_PATH end;
24 } _packed_ efi_initrd_device_path = {
25 .vendor = {
26 .Header = {
27 .Type = MEDIA_DEVICE_PATH,
28 .SubType = MEDIA_VENDOR_DP,
29 .Length = { sizeof(efi_initrd_device_path.vendor), 0 }
30 },
31 .Guid = LINUX_INITRD_MEDIA_GUID
32 },
33 .end = {
34 .Type = END_DEVICE_PATH_TYPE,
35 .SubType = END_ENTIRE_DEVICE_PATH_SUBTYPE,
36 .Length = { sizeof(efi_initrd_device_path.end), 0 }
37 }
38 };
39
initrd_load_file(EFI_LOAD_FILE_PROTOCOL * this,EFI_DEVICE_PATH * file_path,BOOLEAN boot_policy,UINTN * buffer_size,void * buffer)40 EFIAPI EFI_STATUS initrd_load_file(
41 EFI_LOAD_FILE_PROTOCOL *this,
42 EFI_DEVICE_PATH *file_path,
43 BOOLEAN boot_policy,
44 UINTN *buffer_size,
45 void *buffer) {
46
47 struct initrd_loader *loader;
48
49 if (!this || !buffer_size || !file_path)
50 return EFI_INVALID_PARAMETER;
51 if (boot_policy)
52 return EFI_UNSUPPORTED;
53
54 loader = (struct initrd_loader *) this;
55
56 if (loader->length == 0 || !loader->address)
57 return EFI_NOT_FOUND;
58
59 if (!buffer || *buffer_size < loader->length) {
60 *buffer_size = loader->length;
61 return EFI_BUFFER_TOO_SMALL;
62 }
63
64 CopyMem(buffer, loader->address, loader->length);
65 *buffer_size = loader->length;
66 return EFI_SUCCESS;
67 }
68
initrd_register(const void * initrd_address,UINTN initrd_length,EFI_HANDLE * ret_initrd_handle)69 EFI_STATUS initrd_register(
70 const void *initrd_address,
71 UINTN initrd_length,
72 EFI_HANDLE *ret_initrd_handle) {
73
74 EFI_STATUS err;
75 EFI_DEVICE_PATH *dp = (EFI_DEVICE_PATH *) &efi_initrd_device_path;
76 EFI_HANDLE handle;
77 struct initrd_loader *loader;
78
79 assert(ret_initrd_handle);
80
81 if (!initrd_address || initrd_length == 0)
82 return EFI_SUCCESS;
83
84 /* check if a LINUX_INITRD_MEDIA_GUID DevicePath is already registered.
85 LocateDevicePath checks for the "closest DevicePath" and returns its handle,
86 where as InstallMultipleProtocolInterfaces only matches identical DevicePaths.
87 */
88 err = BS->LocateDevicePath(&EfiLoadFile2Protocol, &dp, &handle);
89 if (err != EFI_NOT_FOUND) /* InitrdMedia is already registered */
90 return EFI_ALREADY_STARTED;
91
92 loader = xnew(struct initrd_loader, 1);
93 *loader = (struct initrd_loader) {
94 .load_file.LoadFile = initrd_load_file,
95 .address = initrd_address,
96 .length = initrd_length
97 };
98
99 /* create a new handle and register the LoadFile2 protocol with the InitrdMediaPath on it */
100 err = BS->InstallMultipleProtocolInterfaces(
101 ret_initrd_handle,
102 &DevicePathProtocol, &efi_initrd_device_path,
103 &EfiLoadFile2Protocol, loader,
104 NULL);
105 if (EFI_ERROR(err))
106 FreePool(loader);
107
108 return err;
109 }
110
initrd_unregister(EFI_HANDLE initrd_handle)111 EFI_STATUS initrd_unregister(EFI_HANDLE initrd_handle) {
112 EFI_STATUS err;
113 struct initrd_loader *loader;
114
115 if (!initrd_handle)
116 return EFI_SUCCESS;
117
118 /* get the LoadFile2 protocol that we allocated earlier */
119 err = BS->OpenProtocol(
120 initrd_handle, &EfiLoadFile2Protocol, (void **) &loader,
121 NULL, NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL);
122 if (EFI_ERROR(err))
123 return err;
124
125 /* close the handle */
126 (void) BS->CloseProtocol(initrd_handle, &EfiLoadFile2Protocol, NULL, NULL);
127
128 /* uninstall all protocols thus destroying the handle */
129 err = BS->UninstallMultipleProtocolInterfaces(
130 initrd_handle,
131 &DevicePathProtocol, &efi_initrd_device_path,
132 &EfiLoadFile2Protocol, loader,
133 NULL);
134 if (EFI_ERROR(err))
135 return err;
136
137 initrd_handle = NULL;
138 FreePool(loader);
139 return EFI_SUCCESS;
140 }
141