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