mm.c revision 1.7 1 /* $NetBSD: mm.c,v 1.7 2017/10/29 11:38:43 maxv Exp $ */
2
3 /*
4 * Copyright (c) 2017 The NetBSD Foundation, Inc. All rights reserved.
5 *
6 * This code is derived from software contributed to The NetBSD Foundation
7 * by Maxime Villard.
8 *
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted provided that the following conditions
11 * are met:
12 * 1. Redistributions of source code must retain the above copyright
13 * notice, this list of conditions and the following disclaimer.
14 * 2. Redistributions in binary form must reproduce the above copyright
15 * notice, this list of conditions and the following disclaimer in the
16 * documentation and/or other materials provided with the distribution.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
19 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
20 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
21 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
22 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28 * POSSIBILITY OF SUCH DAMAGE.
29 */
30
31 #include "prekern.h"
32
33 static const pt_entry_t protection_codes[3] = {
34 [MM_PROT_READ] = PG_RO | PG_NX,
35 [MM_PROT_WRITE] = PG_RW | PG_NX,
36 [MM_PROT_EXECUTE] = PG_RO,
37 /* RWX does not exist */
38 };
39
40 struct bootspace bootspace;
41
42 extern paddr_t kernpa_start, kernpa_end;
43 vaddr_t iom_base;
44
45 paddr_t pa_avail = 0;
46 static const vaddr_t tmpva = (PREKERNBASE + NKL2_KIMG_ENTRIES * NBPD_L2);
47
48 void
49 mm_init(paddr_t first_pa)
50 {
51 pa_avail = first_pa;
52 }
53
54 static void
55 mm_enter_pa(paddr_t pa, vaddr_t va, pte_prot_t prot)
56 {
57 PTE_BASE[pl1_i(va)] = pa | PG_V | protection_codes[prot];
58 }
59
60 static void
61 mm_flush_va(vaddr_t va)
62 {
63 asm volatile("invlpg (%0)" ::"r" (va) : "memory");
64 }
65
66 static paddr_t
67 mm_palloc(size_t npages)
68 {
69 paddr_t pa;
70 size_t i;
71
72 /* Allocate the physical pages */
73 pa = pa_avail;
74 pa_avail += npages * PAGE_SIZE;
75
76 /* Zero them out */
77 for (i = 0; i < npages; i++) {
78 mm_enter_pa(pa + i * PAGE_SIZE, tmpva,
79 MM_PROT_READ|MM_PROT_WRITE);
80 mm_flush_va(tmpva);
81 memset((void *)tmpva, 0, PAGE_SIZE);
82 }
83
84 return pa;
85 }
86
87 static bool
88 mm_pte_is_valid(pt_entry_t pte)
89 {
90 return ((pte & PG_V) != 0);
91 }
92
93 paddr_t
94 mm_vatopa(vaddr_t va)
95 {
96 return (PTE_BASE[pl1_i(va)] & PG_FRAME);
97 }
98
99 void
100 mm_mprotect(vaddr_t startva, size_t size, int prot)
101 {
102 size_t i, npages;
103 vaddr_t va;
104 paddr_t pa;
105
106 ASSERT(size % PAGE_SIZE == 0);
107 npages = size / PAGE_SIZE;
108
109 for (i = 0; i < npages; i++) {
110 va = startva + i * PAGE_SIZE;
111 pa = (PTE_BASE[pl1_i(va)] & PG_FRAME);
112 mm_enter_pa(pa, va, prot);
113 mm_flush_va(va);
114 }
115 }
116
117 static size_t
118 mm_nentries_range(vaddr_t startva, vaddr_t endva, size_t pgsz)
119 {
120 size_t npages;
121
122 npages = roundup((endva / PAGE_SIZE), (pgsz / PAGE_SIZE)) -
123 rounddown((startva / PAGE_SIZE), (pgsz / PAGE_SIZE));
124 return (npages / (pgsz / PAGE_SIZE));
125 }
126
127 static void
128 mm_map_tree(vaddr_t startva, vaddr_t endva)
129 {
130 size_t i, nL4e, nL3e, nL2e;
131 size_t L4e_idx, L3e_idx, L2e_idx;
132 paddr_t pa;
133
134 /*
135 * Build L4.
136 */
137 L4e_idx = pl4_i(startva);
138 nL4e = mm_nentries_range(startva, endva, NBPD_L4);
139 ASSERT(L4e_idx == 511);
140 ASSERT(nL4e == 1);
141 if (!mm_pte_is_valid(L4_BASE[L4e_idx])) {
142 pa = mm_palloc(1);
143 L4_BASE[L4e_idx] = pa | PG_V | PG_RW;
144 }
145
146 /*
147 * Build L3.
148 */
149 L3e_idx = pl3_i(startva);
150 nL3e = mm_nentries_range(startva, endva, NBPD_L3);
151 for (i = 0; i < nL3e; i++) {
152 if (mm_pte_is_valid(L3_BASE[L3e_idx+i])) {
153 continue;
154 }
155 pa = mm_palloc(1);
156 L3_BASE[L3e_idx+i] = pa | PG_V | PG_RW;
157 }
158
159 /*
160 * Build L2.
161 */
162 L2e_idx = pl2_i(startva);
163 nL2e = mm_nentries_range(startva, endva, NBPD_L2);
164 for (i = 0; i < nL2e; i++) {
165 if (mm_pte_is_valid(L2_BASE[L2e_idx+i])) {
166 continue;
167 }
168 pa = mm_palloc(1);
169 L2_BASE[L2e_idx+i] = pa | PG_V | PG_RW;
170 }
171 }
172
173 static uint64_t
174 mm_rand_num64()
175 {
176 /* XXX: yes, this is ridiculous, will be fixed soon */
177 return rdtsc();
178 }
179
180 static void
181 mm_map_head()
182 {
183 size_t i, npages, size;
184 uint64_t rnd;
185 vaddr_t randva;
186
187 /*
188 * To get the size of the head, we give a look at the read-only
189 * mapping of the kernel we created in locore. We're identity mapped,
190 * so kernpa = kernva.
191 */
192 size = elf_get_head_size((vaddr_t)kernpa_start);
193 npages = size / PAGE_SIZE;
194
195 rnd = mm_rand_num64();
196 randva = rounddown(HEAD_WINDOW_BASE + rnd % (HEAD_WINDOW_SIZE - size),
197 PAGE_SIZE);
198 mm_map_tree(randva, randva + size);
199
200 /* Enter the area and build the ELF info */
201 for (i = 0; i < npages; i++) {
202 mm_enter_pa(kernpa_start + i * PAGE_SIZE,
203 randva + i * PAGE_SIZE, MM_PROT_READ|MM_PROT_WRITE);
204 }
205 elf_build_head(randva);
206
207 /* Register the values in bootspace */
208 bootspace.head.va = randva;
209 bootspace.head.pa = kernpa_start;
210 bootspace.head.sz = size;
211 }
212
213 static vaddr_t
214 mm_randva_kregion(size_t size)
215 {
216 static struct {
217 vaddr_t sva;
218 vaddr_t eva;
219 } regions[4];
220 static size_t idx = 0;
221 vaddr_t randva;
222 uint64_t rnd;
223 size_t i;
224 bool ok;
225
226 ASSERT(idx < 4);
227
228 while (1) {
229 rnd = mm_rand_num64();
230 randva = rounddown(KASLR_WINDOW_BASE +
231 rnd % (KASLR_WINDOW_SIZE - size), PAGE_SIZE);
232
233 /* Detect collisions */
234 ok = true;
235 for (i = 0; i < idx; i++) {
236 if ((regions[i].sva <= randva) &&
237 (randva < regions[i].eva)) {
238 ok = false;
239 break;
240 }
241 if ((regions[i].sva < randva + size) &&
242 (randva + size <= regions[i].eva)) {
243 ok = false;
244 break;
245 }
246 }
247 if (ok) {
248 break;
249 }
250 }
251
252 regions[idx].eva = randva;
253 regions[idx].sva = randva + size;
254 idx++;
255
256 mm_map_tree(randva, randva + size);
257
258 return randva;
259 }
260
261 static void
262 mm_map_segments()
263 {
264 size_t i, npages, size;
265 vaddr_t randva;
266 paddr_t pa;
267
268 /*
269 * Kernel text segment.
270 */
271 elf_get_text(&pa, &size);
272 randva = mm_randva_kregion(size);
273 npages = size / PAGE_SIZE;
274
275 /* Enter the area and build the ELF info */
276 for (i = 0; i < npages; i++) {
277 mm_enter_pa(pa + i * PAGE_SIZE,
278 randva + i * PAGE_SIZE, MM_PROT_READ|MM_PROT_WRITE);
279 }
280 elf_build_text(randva, pa, size);
281
282 /* Register the values in bootspace */
283 bootspace.text.va = randva;
284 bootspace.text.pa = pa;
285 bootspace.text.sz = size;
286
287 /*
288 * Kernel rodata segment.
289 */
290 elf_get_rodata(&pa, &size);
291 randva = mm_randva_kregion(size);
292 npages = size / PAGE_SIZE;
293
294 /* Enter the area and build the ELF info */
295 for (i = 0; i < npages; i++) {
296 mm_enter_pa(pa + i * PAGE_SIZE,
297 randva + i * PAGE_SIZE, MM_PROT_READ|MM_PROT_WRITE);
298 }
299 elf_build_rodata(randva, pa, size);
300
301 /* Register the values in bootspace */
302 bootspace.rodata.va = randva;
303 bootspace.rodata.pa = pa;
304 bootspace.rodata.sz = size;
305
306 /*
307 * Kernel data segment.
308 */
309 elf_get_data(&pa, &size);
310 randva = mm_randva_kregion(size);
311 npages = size / PAGE_SIZE;
312
313 /* Enter the area and build the ELF info */
314 for (i = 0; i < npages; i++) {
315 mm_enter_pa(pa + i * PAGE_SIZE,
316 randva + i * PAGE_SIZE, MM_PROT_READ|MM_PROT_WRITE);
317 }
318 elf_build_data(randva, pa, size);
319
320 /* Register the values in bootspace */
321 bootspace.data.va = randva;
322 bootspace.data.pa = pa;
323 bootspace.data.sz = size;
324 }
325
326 static void
327 mm_map_boot()
328 {
329 size_t i, npages, size;
330 vaddr_t randva;
331 paddr_t bootpa;
332
333 /*
334 * The "boot" region is special: its page tree has a fixed size, but
335 * the number of pages entered is lower.
336 */
337
338 /* Create the page tree */
339 size = (NKL2_KIMG_ENTRIES + 1) * NBPD_L2;
340 randva = mm_randva_kregion(size);
341
342 /* Enter the area and build the ELF info */
343 bootpa = bootspace.data.pa + bootspace.data.sz;
344 size = (pa_avail - bootpa);
345 npages = size / PAGE_SIZE;
346 for (i = 0; i < npages; i++) {
347 mm_enter_pa(bootpa + i * PAGE_SIZE,
348 randva + i * PAGE_SIZE, MM_PROT_READ|MM_PROT_WRITE);
349 }
350 elf_build_boot(randva, bootpa);
351
352 /* Enter the ISA I/O MEM */
353 iom_base = randva + npages * PAGE_SIZE;
354 npages = IOM_SIZE / PAGE_SIZE;
355 for (i = 0; i < npages; i++) {
356 mm_enter_pa(IOM_BEGIN + i * PAGE_SIZE,
357 iom_base + i * PAGE_SIZE, MM_PROT_READ|MM_PROT_WRITE);
358 }
359
360 /* Register the values in bootspace */
361 bootspace.boot.va = randva;
362 bootspace.boot.pa = bootpa;
363 bootspace.boot.sz = (size_t)(iom_base + IOM_SIZE) -
364 (size_t)bootspace.boot.va;
365
366 /* Initialize the values that are located in the "boot" region */
367 extern uint64_t PDPpaddr;
368 bootspace.spareva = bootspace.boot.va + NKL2_KIMG_ENTRIES * NBPD_L2;
369 bootspace.pdir = bootspace.boot.va + (PDPpaddr - bootspace.boot.pa);
370 bootspace.emodule = bootspace.boot.va + NKL2_KIMG_ENTRIES * NBPD_L2;
371 }
372
373 /*
374 * There are five independent regions: head, text, rodata, data, boot. They are
375 * all mapped at random VAs.
376 *
377 * Head contains the ELF Header and ELF Section Headers, and we use them to
378 * map the rest of the regions. Head must be placed in memory *before* the
379 * other regions.
380 *
381 * At the end of this function, the bootspace structure is fully constructed.
382 */
383 void
384 mm_map_kernel()
385 {
386 memset(&bootspace, 0, sizeof(bootspace));
387 mm_map_head();
388 print_state(true, "Head region mapped");
389 mm_map_segments();
390 print_state(true, "Segments mapped");
391 mm_map_boot();
392 print_state(true, "Boot region mapped");
393 }
394
395