1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * fake_mem.c |
4 | * |
5 | * Copyright (C) 2015 FUJITSU LIMITED |
6 | * Author: Taku Izumi <izumi.taku@jp.fujitsu.com> |
7 | * |
8 | * This code introduces new boot option named "efi_fake_mem" |
9 | * By specifying this parameter, you can add arbitrary attribute to |
10 | * specific memory range by updating original (firmware provided) EFI |
11 | * memmap. |
12 | */ |
13 | |
14 | #include <linux/kernel.h> |
15 | #include <linux/efi.h> |
16 | #include <linux/init.h> |
17 | #include <linux/memblock.h> |
18 | #include <linux/types.h> |
19 | #include <linux/sort.h> |
20 | #include <asm/e820/api.h> |
21 | #include <asm/efi.h> |
22 | |
23 | #define EFI_MAX_FAKEMEM CONFIG_EFI_MAX_FAKE_MEM |
24 | |
25 | static struct efi_mem_range efi_fake_mems[EFI_MAX_FAKEMEM]; |
26 | static int nr_fake_mem; |
27 | |
28 | static int __init cmp_fake_mem(const void *x1, const void *x2) |
29 | { |
30 | const struct efi_mem_range *m1 = x1; |
31 | const struct efi_mem_range *m2 = x2; |
32 | |
33 | if (m1->range.start < m2->range.start) |
34 | return -1; |
35 | if (m1->range.start > m2->range.start) |
36 | return 1; |
37 | return 0; |
38 | } |
39 | |
40 | static void __init efi_fake_range(struct efi_mem_range *efi_range) |
41 | { |
42 | struct efi_memory_map_data data = { 0 }; |
43 | int new_nr_map = efi.memmap.nr_map; |
44 | efi_memory_desc_t *md; |
45 | void *new_memmap; |
46 | |
47 | /* count up the number of EFI memory descriptor */ |
48 | for_each_efi_memory_desc(md) |
49 | new_nr_map += efi_memmap_split_count(md, range: &efi_range->range); |
50 | |
51 | /* allocate memory for new EFI memmap */ |
52 | if (efi_memmap_alloc(num_entries: new_nr_map, data: &data) != 0) |
53 | return; |
54 | |
55 | /* create new EFI memmap */ |
56 | new_memmap = early_memremap(phys_addr: data.phys_map, size: data.size); |
57 | if (!new_memmap) { |
58 | __efi_memmap_free(phys: data.phys_map, size: data.size, flags: data.flags); |
59 | return; |
60 | } |
61 | |
62 | efi_memmap_insert(old_memmap: &efi.memmap, buf: new_memmap, mem: efi_range); |
63 | |
64 | /* swap into new EFI memmap */ |
65 | early_memunmap(addr: new_memmap, size: data.size); |
66 | |
67 | efi_memmap_install(data: &data); |
68 | } |
69 | |
70 | void __init efi_fake_memmap(void) |
71 | { |
72 | int i; |
73 | |
74 | if (!efi_enabled(EFI_MEMMAP) || !nr_fake_mem) |
75 | return; |
76 | |
77 | for (i = 0; i < nr_fake_mem; i++) |
78 | efi_fake_range(efi_range: &efi_fake_mems[i]); |
79 | |
80 | /* print new EFI memmap */ |
81 | efi_print_memmap(); |
82 | } |
83 | |
84 | static int __init setup_fake_mem(char *p) |
85 | { |
86 | u64 start = 0, mem_size = 0, attribute = 0; |
87 | int i; |
88 | |
89 | if (!p) |
90 | return -EINVAL; |
91 | |
92 | while (*p != '\0') { |
93 | mem_size = memparse(ptr: p, retptr: &p); |
94 | if (*p == '@') |
95 | start = memparse(ptr: p+1, retptr: &p); |
96 | else |
97 | break; |
98 | |
99 | if (*p == ':') |
100 | attribute = simple_strtoull(p+1, &p, 0); |
101 | else |
102 | break; |
103 | |
104 | if (nr_fake_mem >= EFI_MAX_FAKEMEM) |
105 | break; |
106 | |
107 | efi_fake_mems[nr_fake_mem].range.start = start; |
108 | efi_fake_mems[nr_fake_mem].range.end = start + mem_size - 1; |
109 | efi_fake_mems[nr_fake_mem].attribute = attribute; |
110 | nr_fake_mem++; |
111 | |
112 | if (*p == ',') |
113 | p++; |
114 | } |
115 | |
116 | sort(base: efi_fake_mems, num: nr_fake_mem, size: sizeof(struct efi_mem_range), |
117 | cmp_func: cmp_fake_mem, NULL); |
118 | |
119 | for (i = 0; i < nr_fake_mem; i++) |
120 | pr_info("efi_fake_mem: add attr=0x%016llx to [mem 0x%016llx-0x%016llx]" , |
121 | efi_fake_mems[i].attribute, efi_fake_mems[i].range.start, |
122 | efi_fake_mems[i].range.end); |
123 | |
124 | return *p == '\0' ? 0 : -EINVAL; |
125 | } |
126 | |
127 | early_param("efi_fake_mem" , setup_fake_mem); |
128 | |
129 | void __init efi_fake_memmap_early(void) |
130 | { |
131 | int i; |
132 | |
133 | /* |
134 | * The late efi_fake_mem() call can handle all requests if |
135 | * EFI_MEMORY_SP support is disabled. |
136 | */ |
137 | if (!efi_soft_reserve_enabled()) |
138 | return; |
139 | |
140 | if (!efi_enabled(EFI_MEMMAP) || !nr_fake_mem) |
141 | return; |
142 | |
143 | /* |
144 | * Given that efi_fake_memmap() needs to perform memblock |
145 | * allocations it needs to run after e820__memblock_setup(). |
146 | * However, if efi_fake_mem specifies EFI_MEMORY_SP for a given |
147 | * address range that potentially needs to mark the memory as |
148 | * reserved prior to e820__memblock_setup(). Update e820 |
149 | * directly if EFI_MEMORY_SP is specified for an |
150 | * EFI_CONVENTIONAL_MEMORY descriptor. |
151 | */ |
152 | for (i = 0; i < nr_fake_mem; i++) { |
153 | struct efi_mem_range *mem = &efi_fake_mems[i]; |
154 | efi_memory_desc_t *md; |
155 | u64 m_start, m_end; |
156 | |
157 | if ((mem->attribute & EFI_MEMORY_SP) == 0) |
158 | continue; |
159 | |
160 | m_start = mem->range.start; |
161 | m_end = mem->range.end; |
162 | for_each_efi_memory_desc(md) { |
163 | u64 start, end, size; |
164 | |
165 | if (md->type != EFI_CONVENTIONAL_MEMORY) |
166 | continue; |
167 | |
168 | start = md->phys_addr; |
169 | end = md->phys_addr + (md->num_pages << EFI_PAGE_SHIFT) - 1; |
170 | |
171 | if (m_start <= end && m_end >= start) |
172 | /* fake range overlaps descriptor */; |
173 | else |
174 | continue; |
175 | |
176 | /* |
177 | * Trim the boundary of the e820 update to the |
178 | * descriptor in case the fake range overlaps |
179 | * !EFI_CONVENTIONAL_MEMORY |
180 | */ |
181 | start = max(start, m_start); |
182 | end = min(end, m_end); |
183 | size = end - start + 1; |
184 | |
185 | if (end <= start) |
186 | continue; |
187 | |
188 | /* |
189 | * Ensure each efi_fake_mem instance results in |
190 | * a unique e820 resource |
191 | */ |
192 | e820__range_remove(start, size, old_type: E820_TYPE_RAM, check_type: 1); |
193 | e820__range_add(start, size, type: E820_TYPE_SOFT_RESERVED); |
194 | e820__update_table(table: e820_table); |
195 | } |
196 | } |
197 | } |
198 | |