1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2 
3 #include <efi.h>
4 
5 #include "devicetree.h"
6 #include "missing_efi.h"
7 #include "util.h"
8 
9 #define FDT_V1_SIZE (7*4)
10 
devicetree_allocate(struct devicetree_state * state,UINTN size)11 static EFI_STATUS devicetree_allocate(struct devicetree_state *state, UINTN size) {
12         UINTN pages = DIV_ROUND_UP(size, EFI_PAGE_SIZE);
13         EFI_STATUS err;
14 
15         assert(state);
16 
17         err = BS->AllocatePages(AllocateAnyPages, EfiACPIReclaimMemory, pages, &state->addr);
18         if (EFI_ERROR(err))
19                 return err;
20 
21         state->pages = pages;
22         return err;
23 }
24 
devicetree_allocated(const struct devicetree_state * state)25 static UINTN devicetree_allocated(const struct devicetree_state *state) {
26         assert(state);
27         return state->pages * EFI_PAGE_SIZE;
28 }
29 
devicetree_fixup(struct devicetree_state * state,UINTN len)30 static EFI_STATUS devicetree_fixup(struct devicetree_state *state, UINTN len) {
31         EFI_DT_FIXUP_PROTOCOL *fixup;
32         UINTN size;
33         EFI_STATUS err;
34 
35         assert(state);
36 
37         err = LibLocateProtocol(&EfiDtFixupProtocol, (void **)&fixup);
38         if (EFI_ERROR(err))
39                 return log_error_status_stall(EFI_SUCCESS,
40                                               L"Could not locate device tree fixup protocol, skipping.");
41 
42         size = devicetree_allocated(state);
43         err = fixup->Fixup(fixup, PHYSICAL_ADDRESS_TO_POINTER(state->addr), &size,
44                            EFI_DT_APPLY_FIXUPS | EFI_DT_RESERVE_MEMORY);
45         if (err == EFI_BUFFER_TOO_SMALL) {
46                 EFI_PHYSICAL_ADDRESS oldaddr = state->addr;
47                 UINTN oldpages = state->pages;
48                 void *oldptr = PHYSICAL_ADDRESS_TO_POINTER(state->addr);
49 
50                 err = devicetree_allocate(state, size);
51                 if (EFI_ERROR(err))
52                         return err;
53 
54                 CopyMem(PHYSICAL_ADDRESS_TO_POINTER(state->addr), oldptr, len);
55                 err = BS->FreePages(oldaddr, oldpages);
56                 if (EFI_ERROR(err))
57                         return err;
58 
59                 size = devicetree_allocated(state);
60                 err = fixup->Fixup(fixup, PHYSICAL_ADDRESS_TO_POINTER(state->addr), &size,
61                                    EFI_DT_APPLY_FIXUPS | EFI_DT_RESERVE_MEMORY);
62         }
63 
64         return err;
65 }
66 
devicetree_install(struct devicetree_state * state,EFI_FILE * root_dir,CHAR16 * name)67 EFI_STATUS devicetree_install(struct devicetree_state *state, EFI_FILE *root_dir, CHAR16 *name) {
68         _cleanup_(file_closep) EFI_FILE *handle = NULL;
69         _cleanup_freepool_ EFI_FILE_INFO *info = NULL;
70         UINTN len;
71         EFI_STATUS err;
72 
73         assert(state);
74         assert(root_dir);
75         assert(name);
76 
77         err = LibGetSystemConfigurationTable(&EfiDtbTableGuid, &state->orig);
78         if (EFI_ERROR(err))
79                 return EFI_UNSUPPORTED;
80 
81         err = root_dir->Open(root_dir, &handle, name, EFI_FILE_MODE_READ, EFI_FILE_READ_ONLY);
82         if (EFI_ERROR(err))
83                 return err;
84 
85         err = get_file_info_harder(handle, &info, NULL);
86         if (EFI_ERROR(err))
87                 return err;
88         if (info->FileSize < FDT_V1_SIZE || info->FileSize > 32 * 1024 * 1024)
89                 /* 32MB device tree blob doesn't seem right */
90                 return EFI_INVALID_PARAMETER;
91 
92         len = info->FileSize;
93 
94         err = devicetree_allocate(state, len);
95         if (EFI_ERROR(err))
96                 return err;
97 
98         err = handle->Read(handle, &len, PHYSICAL_ADDRESS_TO_POINTER(state->addr));
99         if (EFI_ERROR(err))
100                 return err;
101 
102         err = devicetree_fixup(state, len);
103         if (EFI_ERROR(err))
104                 return err;
105 
106         return BS->InstallConfigurationTable(&EfiDtbTableGuid, PHYSICAL_ADDRESS_TO_POINTER(state->addr));
107 }
108 
devicetree_install_from_memory(struct devicetree_state * state,const void * dtb_buffer,UINTN dtb_length)109 EFI_STATUS devicetree_install_from_memory(struct devicetree_state *state,
110                 const void *dtb_buffer, UINTN dtb_length) {
111 
112         EFI_STATUS err;
113 
114         assert(state);
115         assert(dtb_buffer && dtb_length > 0);
116 
117         err = LibGetSystemConfigurationTable(&EfiDtbTableGuid, &state->orig);
118         if (EFI_ERROR(err))
119                 return EFI_UNSUPPORTED;
120 
121         err = devicetree_allocate(state, dtb_length);
122         if (EFI_ERROR(err))
123                 return err;
124 
125         CopyMem(PHYSICAL_ADDRESS_TO_POINTER(state->addr), dtb_buffer, dtb_length);
126 
127         err = devicetree_fixup(state, dtb_length);
128         if (EFI_ERROR(err))
129                 return err;
130 
131         return BS->InstallConfigurationTable(&EfiDtbTableGuid, PHYSICAL_ADDRESS_TO_POINTER(state->addr));
132 }
133 
devicetree_cleanup(struct devicetree_state * state)134 void devicetree_cleanup(struct devicetree_state *state) {
135         EFI_STATUS err;
136 
137         if (!state->pages)
138                 return;
139 
140         err = BS->InstallConfigurationTable(&EfiDtbTableGuid, state->orig);
141         /* don't free the current device tree if we can't reinstate the old one */
142         if (EFI_ERROR(err))
143                 return;
144 
145         BS->FreePages(state->addr, state->pages);
146         state->pages = 0;
147 }
148