Commit | Line | Data |
---|---|---|
0f96a99d TI |
1 | /* |
2 | * fake_mem.c | |
3 | * | |
4 | * Copyright (C) 2015 FUJITSU LIMITED | |
5 | * Author: Taku Izumi <izumi.taku@jp.fujitsu.com> | |
6 | * | |
7 | * This code introduces new boot option named "efi_fake_mem" | |
8 | * By specifying this parameter, you can add arbitrary attribute to | |
9 | * specific memory range by updating original (firmware provided) EFI | |
10 | * memmap. | |
11 | * | |
12 | * This program is free software; you can redistribute it and/or modify it | |
13 | * under the terms and conditions of the GNU General Public License, | |
14 | * version 2, as published by the Free Software Foundation. | |
15 | * | |
16 | * This program is distributed in the hope it will be useful, but WITHOUT | |
17 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
18 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | |
19 | * more details. | |
20 | * | |
21 | * You should have received a copy of the GNU General Public License along with | |
22 | * this program; if not, see <http://www.gnu.org/licenses/>. | |
23 | * | |
24 | * The full GNU General Public License is included in this distribution in | |
25 | * the file called "COPYING". | |
26 | */ | |
27 | ||
28 | #include <linux/kernel.h> | |
29 | #include <linux/efi.h> | |
30 | #include <linux/init.h> | |
31 | #include <linux/memblock.h> | |
32 | #include <linux/types.h> | |
33 | #include <linux/sort.h> | |
34 | #include <asm/efi.h> | |
35 | ||
36 | #define EFI_MAX_FAKEMEM CONFIG_EFI_MAX_FAKE_MEM | |
37 | ||
38 | struct fake_mem { | |
39 | struct range range; | |
40 | u64 attribute; | |
41 | }; | |
42 | static struct fake_mem fake_mems[EFI_MAX_FAKEMEM]; | |
43 | static int nr_fake_mem; | |
44 | ||
45 | static int __init cmp_fake_mem(const void *x1, const void *x2) | |
46 | { | |
47 | const struct fake_mem *m1 = x1; | |
48 | const struct fake_mem *m2 = x2; | |
49 | ||
50 | if (m1->range.start < m2->range.start) | |
51 | return -1; | |
52 | if (m1->range.start > m2->range.start) | |
53 | return 1; | |
54 | return 0; | |
55 | } | |
56 | ||
57 | void __init efi_fake_memmap(void) | |
58 | { | |
59 | u64 start, end, m_start, m_end, m_attr; | |
60 | int new_nr_map = memmap.nr_map; | |
61 | efi_memory_desc_t *md; | |
78b9bc94 | 62 | phys_addr_t new_memmap_phy; |
0f96a99d TI |
63 | void *new_memmap; |
64 | void *old, *new; | |
65 | int i; | |
66 | ||
67 | if (!nr_fake_mem || !efi_enabled(EFI_MEMMAP)) | |
68 | return; | |
69 | ||
70 | /* count up the number of EFI memory descriptor */ | |
71 | for (old = memmap.map; old < memmap.map_end; old += memmap.desc_size) { | |
72 | md = old; | |
73 | start = md->phys_addr; | |
74 | end = start + (md->num_pages << EFI_PAGE_SHIFT) - 1; | |
75 | ||
76 | for (i = 0; i < nr_fake_mem; i++) { | |
77 | /* modifying range */ | |
78 | m_start = fake_mems[i].range.start; | |
79 | m_end = fake_mems[i].range.end; | |
80 | ||
81 | if (m_start <= start) { | |
82 | /* split into 2 parts */ | |
83 | if (start < m_end && m_end < end) | |
84 | new_nr_map++; | |
85 | } | |
86 | if (start < m_start && m_start < end) { | |
87 | /* split into 3 parts */ | |
88 | if (m_end < end) | |
89 | new_nr_map += 2; | |
90 | /* split into 2 parts */ | |
91 | if (end <= m_end) | |
92 | new_nr_map++; | |
93 | } | |
94 | } | |
95 | } | |
96 | ||
97 | /* allocate memory for new EFI memmap */ | |
98 | new_memmap_phy = memblock_alloc(memmap.desc_size * new_nr_map, | |
99 | PAGE_SIZE); | |
100 | if (!new_memmap_phy) | |
101 | return; | |
102 | ||
103 | /* create new EFI memmap */ | |
104 | new_memmap = early_memremap(new_memmap_phy, | |
105 | memmap.desc_size * new_nr_map); | |
106 | if (!new_memmap) { | |
107 | memblock_free(new_memmap_phy, memmap.desc_size * new_nr_map); | |
108 | return; | |
109 | } | |
110 | ||
111 | for (old = memmap.map, new = new_memmap; | |
112 | old < memmap.map_end; | |
113 | old += memmap.desc_size, new += memmap.desc_size) { | |
114 | ||
115 | /* copy original EFI memory descriptor */ | |
116 | memcpy(new, old, memmap.desc_size); | |
117 | md = new; | |
118 | start = md->phys_addr; | |
119 | end = md->phys_addr + (md->num_pages << EFI_PAGE_SHIFT) - 1; | |
120 | ||
121 | for (i = 0; i < nr_fake_mem; i++) { | |
122 | /* modifying range */ | |
123 | m_start = fake_mems[i].range.start; | |
124 | m_end = fake_mems[i].range.end; | |
125 | m_attr = fake_mems[i].attribute; | |
126 | ||
127 | if (m_start <= start && end <= m_end) | |
128 | md->attribute |= m_attr; | |
129 | ||
130 | if (m_start <= start && | |
131 | (start < m_end && m_end < end)) { | |
132 | /* first part */ | |
133 | md->attribute |= m_attr; | |
134 | md->num_pages = (m_end - md->phys_addr + 1) >> | |
135 | EFI_PAGE_SHIFT; | |
136 | /* latter part */ | |
137 | new += memmap.desc_size; | |
138 | memcpy(new, old, memmap.desc_size); | |
139 | md = new; | |
140 | md->phys_addr = m_end + 1; | |
141 | md->num_pages = (end - md->phys_addr + 1) >> | |
142 | EFI_PAGE_SHIFT; | |
143 | } | |
144 | ||
145 | if ((start < m_start && m_start < end) && m_end < end) { | |
146 | /* first part */ | |
147 | md->num_pages = (m_start - md->phys_addr) >> | |
148 | EFI_PAGE_SHIFT; | |
149 | /* middle part */ | |
150 | new += memmap.desc_size; | |
151 | memcpy(new, old, memmap.desc_size); | |
152 | md = new; | |
153 | md->attribute |= m_attr; | |
154 | md->phys_addr = m_start; | |
155 | md->num_pages = (m_end - m_start + 1) >> | |
156 | EFI_PAGE_SHIFT; | |
157 | /* last part */ | |
158 | new += memmap.desc_size; | |
159 | memcpy(new, old, memmap.desc_size); | |
160 | md = new; | |
161 | md->phys_addr = m_end + 1; | |
162 | md->num_pages = (end - m_end) >> | |
163 | EFI_PAGE_SHIFT; | |
164 | } | |
165 | ||
166 | if ((start < m_start && m_start < end) && | |
167 | (end <= m_end)) { | |
168 | /* first part */ | |
169 | md->num_pages = (m_start - md->phys_addr) >> | |
170 | EFI_PAGE_SHIFT; | |
171 | /* latter part */ | |
172 | new += memmap.desc_size; | |
173 | memcpy(new, old, memmap.desc_size); | |
174 | md = new; | |
175 | md->phys_addr = m_start; | |
176 | md->num_pages = (end - md->phys_addr + 1) >> | |
177 | EFI_PAGE_SHIFT; | |
178 | md->attribute |= m_attr; | |
179 | } | |
180 | } | |
181 | } | |
182 | ||
183 | /* swap into new EFI memmap */ | |
184 | efi_unmap_memmap(); | |
185 | memmap.map = new_memmap; | |
78b9bc94 | 186 | memmap.phys_map = new_memmap_phy; |
0f96a99d TI |
187 | memmap.nr_map = new_nr_map; |
188 | memmap.map_end = memmap.map + memmap.nr_map * memmap.desc_size; | |
189 | set_bit(EFI_MEMMAP, &efi.flags); | |
190 | ||
191 | /* print new EFI memmap */ | |
192 | efi_print_memmap(); | |
193 | } | |
194 | ||
195 | static int __init setup_fake_mem(char *p) | |
196 | { | |
197 | u64 start = 0, mem_size = 0, attribute = 0; | |
198 | int i; | |
199 | ||
200 | if (!p) | |
201 | return -EINVAL; | |
202 | ||
203 | while (*p != '\0') { | |
204 | mem_size = memparse(p, &p); | |
205 | if (*p == '@') | |
206 | start = memparse(p+1, &p); | |
207 | else | |
208 | break; | |
209 | ||
210 | if (*p == ':') | |
211 | attribute = simple_strtoull(p+1, &p, 0); | |
212 | else | |
213 | break; | |
214 | ||
215 | if (nr_fake_mem >= EFI_MAX_FAKEMEM) | |
216 | break; | |
217 | ||
218 | fake_mems[nr_fake_mem].range.start = start; | |
219 | fake_mems[nr_fake_mem].range.end = start + mem_size - 1; | |
220 | fake_mems[nr_fake_mem].attribute = attribute; | |
221 | nr_fake_mem++; | |
222 | ||
223 | if (*p == ',') | |
224 | p++; | |
225 | } | |
226 | ||
227 | sort(fake_mems, nr_fake_mem, sizeof(struct fake_mem), | |
228 | cmp_fake_mem, NULL); | |
229 | ||
230 | for (i = 0; i < nr_fake_mem; i++) | |
231 | pr_info("efi_fake_mem: add attr=0x%016llx to [mem 0x%016llx-0x%016llx]", | |
232 | fake_mems[i].attribute, fake_mems[i].range.start, | |
233 | fake_mems[i].range.end); | |
234 | ||
235 | return *p == '\0' ? 0 : -EINVAL; | |
236 | } | |
237 | ||
238 | early_param("efi_fake_mem", setup_fake_mem); |