Commit | Line | Data |
---|---|---|
745e3ed8 KS |
1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | ||
3 | #include <linux/efi.h> | |
4 | #include <asm/efi.h> | |
5 | #include "efistub.h" | |
6 | ||
7 | struct efi_unaccepted_memory *unaccepted_table; | |
8 | ||
9 | efi_status_t allocate_unaccepted_bitmap(__u32 nr_desc, | |
10 | struct efi_boot_memmap *map) | |
11 | { | |
12 | efi_guid_t unaccepted_table_guid = LINUX_EFI_UNACCEPTED_MEM_TABLE_GUID; | |
13 | u64 unaccepted_start = ULLONG_MAX, unaccepted_end = 0, bitmap_size; | |
14 | efi_status_t status; | |
15 | int i; | |
16 | ||
17 | /* Check if the table is already installed */ | |
18 | unaccepted_table = get_efi_config_table(unaccepted_table_guid); | |
19 | if (unaccepted_table) { | |
20 | if (unaccepted_table->version != 1) { | |
21 | efi_err("Unknown version of unaccepted memory table\n"); | |
22 | return EFI_UNSUPPORTED; | |
23 | } | |
24 | return EFI_SUCCESS; | |
25 | } | |
26 | ||
27 | /* Check if there's any unaccepted memory and find the max address */ | |
28 | for (i = 0; i < nr_desc; i++) { | |
29 | efi_memory_desc_t *d; | |
30 | unsigned long m = (unsigned long)map->map; | |
31 | ||
32 | d = efi_early_memdesc_ptr(m, map->desc_size, i); | |
33 | if (d->type != EFI_UNACCEPTED_MEMORY) | |
34 | continue; | |
35 | ||
36 | unaccepted_start = min(unaccepted_start, d->phys_addr); | |
37 | unaccepted_end = max(unaccepted_end, | |
38 | d->phys_addr + d->num_pages * PAGE_SIZE); | |
39 | } | |
40 | ||
41 | if (unaccepted_start == ULLONG_MAX) | |
42 | return EFI_SUCCESS; | |
43 | ||
44 | unaccepted_start = round_down(unaccepted_start, | |
45 | EFI_UNACCEPTED_UNIT_SIZE); | |
46 | unaccepted_end = round_up(unaccepted_end, EFI_UNACCEPTED_UNIT_SIZE); | |
47 | ||
48 | /* | |
49 | * If unaccepted memory is present, allocate a bitmap to track what | |
50 | * memory has to be accepted before access. | |
51 | * | |
52 | * One bit in the bitmap represents 2MiB in the address space: | |
53 | * A 4k bitmap can track 64GiB of physical address space. | |
54 | * | |
55 | * In the worst case scenario -- a huge hole in the middle of the | |
56 | * address space -- It needs 256MiB to handle 4PiB of the address | |
57 | * space. | |
58 | * | |
59 | * The bitmap will be populated in setup_e820() according to the memory | |
60 | * map after efi_exit_boot_services(). | |
61 | */ | |
62 | bitmap_size = DIV_ROUND_UP(unaccepted_end - unaccepted_start, | |
63 | EFI_UNACCEPTED_UNIT_SIZE * BITS_PER_BYTE); | |
64 | ||
e7761d82 | 65 | status = efi_bs_call(allocate_pool, EFI_ACPI_RECLAIM_MEMORY, |
745e3ed8 KS |
66 | sizeof(*unaccepted_table) + bitmap_size, |
67 | (void **)&unaccepted_table); | |
68 | if (status != EFI_SUCCESS) { | |
69 | efi_err("Failed to allocate unaccepted memory config table\n"); | |
70 | return status; | |
71 | } | |
72 | ||
73 | unaccepted_table->version = 1; | |
74 | unaccepted_table->unit_size = EFI_UNACCEPTED_UNIT_SIZE; | |
75 | unaccepted_table->phys_base = unaccepted_start; | |
76 | unaccepted_table->size = bitmap_size; | |
77 | memset(unaccepted_table->bitmap, 0, bitmap_size); | |
78 | ||
79 | status = efi_bs_call(install_configuration_table, | |
80 | &unaccepted_table_guid, unaccepted_table); | |
81 | if (status != EFI_SUCCESS) { | |
82 | efi_bs_call(free_pool, unaccepted_table); | |
83 | efi_err("Failed to install unaccepted memory config table!\n"); | |
84 | } | |
85 | ||
86 | return status; | |
87 | } | |
88 | ||
89 | /* | |
90 | * The accepted memory bitmap only works at unit_size granularity. Take | |
91 | * unaligned start/end addresses and either: | |
92 | * 1. Accepts the memory immediately and in its entirety | |
93 | * 2. Accepts unaligned parts, and marks *some* aligned part unaccepted | |
94 | * | |
95 | * The function will never reach the bitmap_set() with zero bits to set. | |
96 | */ | |
97 | void process_unaccepted_memory(u64 start, u64 end) | |
98 | { | |
99 | u64 unit_size = unaccepted_table->unit_size; | |
100 | u64 unit_mask = unaccepted_table->unit_size - 1; | |
101 | u64 bitmap_size = unaccepted_table->size; | |
102 | ||
103 | /* | |
104 | * Ensure that at least one bit will be set in the bitmap by | |
105 | * immediately accepting all regions under 2*unit_size. This is | |
106 | * imprecise and may immediately accept some areas that could | |
107 | * have been represented in the bitmap. But, results in simpler | |
108 | * code below | |
109 | * | |
110 | * Consider case like this (assuming unit_size == 2MB): | |
111 | * | |
112 | * | 4k | 2044k | 2048k | | |
113 | * ^ 0x0 ^ 2MB ^ 4MB | |
114 | * | |
115 | * Only the first 4k has been accepted. The 0MB->2MB region can not be | |
116 | * represented in the bitmap. The 2MB->4MB region can be represented in | |
117 | * the bitmap. But, the 0MB->4MB region is <2*unit_size and will be | |
118 | * immediately accepted in its entirety. | |
119 | */ | |
120 | if (end - start < 2 * unit_size) { | |
121 | arch_accept_memory(start, end); | |
122 | return; | |
123 | } | |
124 | ||
125 | /* | |
126 | * No matter how the start and end are aligned, at least one unaccepted | |
127 | * unit_size area will remain to be marked in the bitmap. | |
128 | */ | |
129 | ||
130 | /* Immediately accept a <unit_size piece at the start: */ | |
131 | if (start & unit_mask) { | |
132 | arch_accept_memory(start, round_up(start, unit_size)); | |
133 | start = round_up(start, unit_size); | |
134 | } | |
135 | ||
136 | /* Immediately accept a <unit_size piece at the end: */ | |
137 | if (end & unit_mask) { | |
138 | arch_accept_memory(round_down(end, unit_size), end); | |
139 | end = round_down(end, unit_size); | |
140 | } | |
141 | ||
142 | /* | |
143 | * Accept part of the range that before phys_base and cannot be recorded | |
144 | * into the bitmap. | |
145 | */ | |
146 | if (start < unaccepted_table->phys_base) { | |
147 | arch_accept_memory(start, | |
148 | min(unaccepted_table->phys_base, end)); | |
149 | start = unaccepted_table->phys_base; | |
150 | } | |
151 | ||
152 | /* Nothing to record */ | |
153 | if (end < unaccepted_table->phys_base) | |
154 | return; | |
155 | ||
156 | /* Translate to offsets from the beginning of the bitmap */ | |
157 | start -= unaccepted_table->phys_base; | |
158 | end -= unaccepted_table->phys_base; | |
159 | ||
160 | /* Accept memory that doesn't fit into bitmap */ | |
161 | if (end > bitmap_size * unit_size * BITS_PER_BYTE) { | |
162 | unsigned long phys_start, phys_end; | |
163 | ||
164 | phys_start = bitmap_size * unit_size * BITS_PER_BYTE + | |
165 | unaccepted_table->phys_base; | |
166 | phys_end = end + unaccepted_table->phys_base; | |
167 | ||
168 | arch_accept_memory(phys_start, phys_end); | |
169 | end = bitmap_size * unit_size * BITS_PER_BYTE; | |
170 | } | |
171 | ||
172 | /* | |
173 | * 'start' and 'end' are now both unit_size-aligned. | |
174 | * Record the range as being unaccepted: | |
175 | */ | |
176 | bitmap_set(unaccepted_table->bitmap, | |
177 | start / unit_size, (end - start) / unit_size); | |
178 | } | |
179 | ||
180 | void accept_memory(phys_addr_t start, phys_addr_t end) | |
181 | { | |
182 | unsigned long range_start, range_end; | |
183 | unsigned long bitmap_size; | |
184 | u64 unit_size; | |
185 | ||
186 | if (!unaccepted_table) | |
187 | return; | |
188 | ||
189 | unit_size = unaccepted_table->unit_size; | |
190 | ||
191 | /* | |
192 | * Only care for the part of the range that is represented | |
193 | * in the bitmap. | |
194 | */ | |
195 | if (start < unaccepted_table->phys_base) | |
196 | start = unaccepted_table->phys_base; | |
197 | if (end < unaccepted_table->phys_base) | |
198 | return; | |
199 | ||
200 | /* Translate to offsets from the beginning of the bitmap */ | |
201 | start -= unaccepted_table->phys_base; | |
202 | end -= unaccepted_table->phys_base; | |
203 | ||
204 | /* Make sure not to overrun the bitmap */ | |
205 | if (end > unaccepted_table->size * unit_size * BITS_PER_BYTE) | |
206 | end = unaccepted_table->size * unit_size * BITS_PER_BYTE; | |
207 | ||
208 | range_start = start / unit_size; | |
209 | bitmap_size = DIV_ROUND_UP(end, unit_size); | |
210 | ||
211 | for_each_set_bitrange_from(range_start, range_end, | |
212 | unaccepted_table->bitmap, bitmap_size) { | |
213 | unsigned long phys_start, phys_end; | |
214 | ||
215 | phys_start = range_start * unit_size + unaccepted_table->phys_base; | |
216 | phys_end = range_end * unit_size + unaccepted_table->phys_base; | |
217 | ||
218 | arch_accept_memory(phys_start, phys_end); | |
219 | bitmap_clear(unaccepted_table->bitmap, | |
220 | range_start, range_end - range_start); | |
221 | } | |
222 | } |