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