|
| 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 | + |
| 65 | + status = efi_bs_call(allocate_pool, EFI_LOADER_DATA, |
| 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 | +} |
0 commit comments