xref: /haiku/src/system/kernel/arch/x86/arch_vm.cpp (revision 16d5c24e533eb14b7b8a99ee9f3ec9ba66335b1e)
1 /*
2  * Copyright 2002-2007, Axel Dörfler, axeld@pinc-software.de.
3  * Copyright 2008, Jérôme Duval.
4  * Distributed under the terms of the MIT License.
5  *
6  * Copyright 2001, Travis Geiselbrecht. All rights reserved.
7  * Distributed under the terms of the NewOS License.
8  */
9 
10 
11 #include <stdlib.h>
12 #include <string.h>
13 
14 #include <KernelExport.h>
15 
16 #include <smp.h>
17 #include <util/AutoLock.h>
18 #include <vm.h>
19 #include <vm_address_space.h>
20 #include <vm_page.h>
21 #include <vm_priv.h>
22 
23 #include <arch/vm.h>
24 #include <arch/int.h>
25 #include <arch/cpu.h>
26 
27 #include <arch/x86/bios.h>
28 
29 #include "x86_paging.h"
30 
31 
32 //#define TRACE_ARCH_VM
33 #ifdef TRACE_ARCH_VM
34 #	define TRACE(x) dprintf x
35 #else
36 #	define TRACE(x) ;
37 #endif
38 
39 #define TRACE_MTRR_ARCH_VM
40 #ifdef TRACE_MTRR_ARCH_VM
41 #	define TRACE_MTRR(x...) dprintf(x)
42 #else
43 #	define TRACE_MTRR(x...)
44 #endif
45 
46 
47 #define kMaxMemoryTypeRegisters 32
48 
49 void *gDmaAddress;
50 
51 static uint32 sMemoryTypeBitmap;
52 static int32 sMemoryTypeIDs[kMaxMemoryTypeRegisters];
53 static uint32 sMemoryTypeRegisterCount;
54 static spinlock sMemoryTypeLock;
55 
56 
57 static int32
58 allocate_mtrr(void)
59 {
60 	InterruptsSpinLocker _(&sMemoryTypeLock);
61 
62 	// find free bit
63 
64 	for (uint32 index = 0; index < sMemoryTypeRegisterCount; index++) {
65 		if (sMemoryTypeBitmap & (1UL << index))
66 			continue;
67 
68 		sMemoryTypeBitmap |= 1UL << index;
69 		return index;
70 	}
71 
72 	return -1;
73 }
74 
75 
76 static void
77 free_mtrr(int32 index)
78 {
79 	InterruptsSpinLocker _(&sMemoryTypeLock);
80 
81 	sMemoryTypeBitmap &= ~(1UL << index);
82 }
83 
84 
85 #if 0
86 /*!
87  	Checks if the provided range overlaps an existing mtrr range
88  	If it actually extends an existing range, extendedIndex is filled
89 */
90 static bool
91 is_memory_overlapping(uint64 base, uint64 length, int32 *extendedIndex)
92 {
93 	*extendedIndex = -1;
94 	for (uint32 index = 0; index < sMemoryTypeRegisterCount; index++) {
95 		if (sMemoryTypeBitmap & (1UL << index)) {
96 			uint64 b,l;
97 			uint8 t;
98 			x86_get_mtrr(index, &b, &l, &t);
99 
100 			// check first for write combining extensions
101 			if (base <= b
102 				&& (base + length) >= (b + l)
103 				&& t == IA32_MTR_WRITE_COMBINING) {
104 				*extendedIndex = index;
105 				return true;
106 			}
107 			if ((base >= b && base < (b + l))
108 				|| ((base + length) > b
109 					&& (base + length) <= (b + l)))
110 				return true;
111 		}
112 	}
113 	return false;
114 }
115 #endif	// 0
116 
117 
118 static uint64
119 nearest_power(uint64 value)
120 {
121 	uint64 power = 1UL << 12;
122 		// 12 bits is the smallest supported alignment/length
123 
124 	while (value > power)
125 		power <<= 1;
126 
127 	return power;
128 }
129 
130 
131 static void
132 nearest_powers(uint64 value, uint64 *lower, uint64 *upper)
133 {
134 	uint64 power = 1UL << 12;
135 	*lower = power;
136 		// 12 bits is the smallest supported alignment/length
137 
138 	while (value >= power) {
139 		*lower = power;
140 		power <<= 1;
141 	}
142 
143 	*upper = power;
144 }
145 
146 
147 static status_t
148 set_memory_type(int32 id, uint64 base, uint64 length, uint32 type)
149 {
150 	int32 index = -1;
151 
152 	if (type == 0)
153 		return B_OK;
154 
155 	switch (type) {
156 		case B_MTR_UC:
157 			type = IA32_MTR_UNCACHED;
158 			break;
159 		case B_MTR_WC:
160 			type = IA32_MTR_WRITE_COMBINING;
161 			break;
162 		case B_MTR_WT:
163 			type = IA32_MTR_WRITE_THROUGH;
164 			break;
165 		case B_MTR_WP:
166 			type = IA32_MTR_WRITE_PROTECTED;
167 			break;
168 		case B_MTR_WB:
169 			type = IA32_MTR_WRITE_BACK;
170 			break;
171 		default:
172 			return B_BAD_VALUE;
173 	}
174 
175 	if (sMemoryTypeRegisterCount == 0)
176 		return B_NOT_SUPPORTED;
177 
178 #if 0
179 	// check if it overlaps
180 	if (type == IA32_MTR_WRITE_COMBINING
181 		&& is_memory_overlapping(base, length, &index)) {
182 		if (index < 0) {
183 			dprintf("allocate MTRR failed, it overlaps an existing MTRR slot\n");
184 			return B_BAD_VALUE;
185 		}
186 		// we replace an existing write-combining mtrr with a bigger one at the index position
187 	}
188 #endif
189 
190 	// length must be a power of 2; just round it up to the next value
191 	length = nearest_power(length);
192 
193 	if (length + base <= base) {
194 		// 4GB overflow
195 		return B_BAD_VALUE;
196 	}
197 
198 	// base must be aligned to the length
199 	if (base & (length - 1))
200 		return B_BAD_VALUE;
201 
202 	if (index < 0)
203 		index = allocate_mtrr();
204 	if (index < 0)
205 		return B_ERROR;
206 
207 	TRACE_MTRR("allocate MTRR slot %ld, base = %Lx, length = %Lx, type=0x%lx\n",
208 		index, base, length, type);
209 
210 	sMemoryTypeIDs[index] = id;
211 	x86_set_mtrr(index, base, length, type);
212 
213 	return B_OK;
214 }
215 
216 
217 #define MTRR_MAX_SOLUTIONS 	5	// usually MTRR count is eight, keep a few for other needs
218 #define MTRR_MIN_SIZE 		0x80000	// 512 KB
219 static int64 sSolutions[MTRR_MAX_SOLUTIONS];
220 static int32 sSolutionCount;
221 static int64 sPropositions[MTRR_MAX_SOLUTIONS];
222 
223 
224 /*!	Find the nearest powers of two for a value, save current iteration,
225   	then make recursives calls for the remaining values.
226   	It uses at most MTRR_MAX_SOLUTIONS levels of recursion because
227   	only that count of MTRR registers are available to map the memory.
228 */
229 static void
230 find_nearest(uint64 value, int iteration)
231 {
232 	int i;
233 	uint64 down, up;
234 	TRACE_MTRR("find_nearest %Lx %d\n", value, iteration);
235 	if (iteration > (MTRR_MAX_SOLUTIONS - 1) || (iteration + 1) >= sSolutionCount) {
236 		if (sSolutionCount > MTRR_MAX_SOLUTIONS) {
237 			// no solutions yet, save something
238 			for (i=0; i<iteration; i++)
239 				sSolutions[i] = sPropositions[i];
240 			sSolutionCount = iteration;
241 		}
242 		return;
243 	}
244 	nearest_powers(value, &down, &up);
245 	sPropositions[iteration] = down;
246 	if (value - down < MTRR_MIN_SIZE) {
247 		for (i=0; i<=iteration; i++)
248 			sSolutions[i] = sPropositions[i];
249 		sSolutionCount = iteration + 1;
250 		return;
251 	}
252 	find_nearest(value - down, iteration + 1);
253 	sPropositions[iteration] = -up;
254 	if (up - value < MTRR_MIN_SIZE) {
255 		for (i=0; i<=iteration; i++)
256 			sSolutions[i] = sPropositions[i];
257 		sSolutionCount = iteration + 1;
258 		return;
259 	}
260 	find_nearest(up - value, iteration + 1);
261 }
262 
263 
264 /*!	Set up MTRR to map the memory to write-back using uncached if necessary */
265 static void
266 set_memory_write_back(int32 id, uint64 base, uint64 length)
267 {
268 	status_t err;
269 	TRACE_MTRR("set_memory_write_back base %Lx length %Lx\n", base, length);
270 	sSolutionCount = MTRR_MAX_SOLUTIONS + 1;
271 	find_nearest(length, 0);
272 
273 #ifdef TRACE_MTRR
274 	dprintf("solutions: ");
275 	for (int i=0; i<sSolutionCount; i++)
276 		dprintf("0x%Lx ", sSolutions[i]);
277 	dprintf("\n");
278 #endif
279 
280 	for (int run = 0; run < 2; run++) {
281 		bool nextDown = false;
282 		uint64 newBase = base;
283 
284 		for (int i = 0; i < sSolutionCount; i++) {
285 			if (sSolutions[i] < 0) {
286 				if (nextDown)
287 					newBase += sSolutions[i];
288 
289 				if ((run == 0) == nextDown) {
290 					err = set_memory_type(id, newBase, -sSolutions[i],
291 						nextDown ? B_MTR_UC : B_MTR_WB);
292 					if (err != B_OK) {
293 						dprintf("set_memory_type returned %s (0x%lx)\n",
294 							strerror(err), err);
295 					}
296 				}
297 
298 				if (!nextDown)
299 					newBase -= sSolutions[i];
300 				nextDown = !nextDown;
301 			} else {
302 				if (nextDown)
303 					newBase -= sSolutions[i];
304 
305 				if ((run == 0) == nextDown) {
306 					err = set_memory_type(id, newBase, sSolutions[i],
307 						nextDown ? B_MTR_UC : B_MTR_WB);
308 					if (err != B_OK) {
309 						dprintf("set_memory_type returned %s (0x%lx)\n",
310 							strerror(err), err);
311 					}
312 				}
313 
314 				if (!nextDown)
315 					newBase += sSolutions[i];
316 			}
317 		}
318 	}
319 }
320 
321 
322 //	#pragma mark -
323 
324 
325 status_t
326 arch_vm_init(kernel_args *args)
327 {
328 	TRACE(("arch_vm_init: entry\n"));
329 	return 0;
330 }
331 
332 
333 /*!	Marks DMA region as in-use, and maps it into the kernel space */
334 status_t
335 arch_vm_init_post_area(kernel_args *args)
336 {
337 	area_id id;
338 
339 	TRACE(("arch_vm_init_post_area: entry\n"));
340 
341 	// account for DMA area and mark the pages unusable
342 	vm_mark_page_range_inuse(0x0, 0xa0000 / B_PAGE_SIZE);
343 
344 	// map 0 - 0xa0000 directly
345 	id = map_physical_memory("dma_region", (void *)0x0, 0xa0000,
346 		B_ANY_KERNEL_ADDRESS, B_KERNEL_READ_AREA | B_KERNEL_WRITE_AREA,
347 		&gDmaAddress);
348 	if (id < 0) {
349 		panic("arch_vm_init_post_area: unable to map dma region\n");
350 		return B_NO_MEMORY;
351 	}
352 
353 	return bios_init();
354 }
355 
356 
357 /*!	Gets rid of all yet unmapped (and therefore now unused) page tables */
358 status_t
359 arch_vm_init_end(kernel_args *args)
360 {
361 	TRACE(("arch_vm_init_endvm: entry\n"));
362 
363 	// throw away anything in the kernel_args.pgtable[] that's not yet mapped
364 	vm_free_unused_boot_loader_range(KERNEL_BASE,
365 		0x400000 * args->arch_args.num_pgtables);
366 
367 	return B_OK;
368 }
369 
370 
371 status_t
372 arch_vm_init_post_modules(kernel_args *args)
373 {
374 //	void *cookie;
375 
376 	// the x86 CPU modules are now accessible
377 
378 	sMemoryTypeRegisterCount = x86_count_mtrrs();
379 	if (sMemoryTypeRegisterCount == 0)
380 		return B_OK;
381 
382 	// not very likely, but play safe here
383 	if (sMemoryTypeRegisterCount > kMaxMemoryTypeRegisters)
384 		sMemoryTypeRegisterCount = kMaxMemoryTypeRegisters;
385 
386 	// init memory type ID table
387 
388 	for (uint32 i = 0; i < sMemoryTypeRegisterCount; i++) {
389 		sMemoryTypeIDs[i] = -1;
390 	}
391 
392 	// set the physical memory ranges to write-back mode
393 
394 	for (uint32 i = 0; i < args->num_physical_memory_ranges; i++) {
395 		set_memory_write_back(-1, args->physical_memory_range[i].start,
396 			args->physical_memory_range[i].size);
397 	}
398 
399 	return B_OK;
400 }
401 
402 
403 void
404 arch_vm_aspace_swap(struct vm_address_space *from, struct vm_address_space *to)
405 {
406 	// This functions is only invoked when a userland thread is in the process
407 	// of dying. It switches to the kernel team and does whatever cleanup is
408 	// necessary (in case it is the team's main thread, it will delete the
409 	// team).
410 	// It is however not necessary to change the page directory. Userland team's
411 	// page directories include all kernel mappings as well. Furthermore our
412 	// arch specific translation map data objects are ref-counted, so they won't
413 	// go away as long as they are still used on any CPU.
414 }
415 
416 
417 bool
418 arch_vm_supports_protection(uint32 protection)
419 {
420 	// x86 always has the same read/write properties for userland and the
421 	// kernel.
422 	// That's why we do not support user-read/kernel-write access. While the
423 	// other way around is not supported either, we don't care in this case
424 	// and give the kernel full access.
425 	if ((protection & (B_READ_AREA | B_WRITE_AREA)) == B_READ_AREA
426 		&& protection & B_KERNEL_WRITE_AREA)
427 		return false;
428 
429 	return true;
430 }
431 
432 
433 void
434 arch_vm_unset_memory_type(struct vm_area *area)
435 {
436 	uint32 index;
437 
438 	if (area->memory_type == 0)
439 		return;
440 
441 	// find index for area ID
442 
443 	for (index = 0; index < sMemoryTypeRegisterCount; index++) {
444 		if (sMemoryTypeIDs[index] == area->id) {
445 			x86_set_mtrr(index, 0, 0, 0);
446 
447 			sMemoryTypeIDs[index] = -1;
448 			free_mtrr(index);
449 			break;
450 		}
451 	}
452 }
453 
454 
455 status_t
456 arch_vm_set_memory_type(struct vm_area *area, addr_t physicalBase,
457 	uint32 type)
458 {
459 	area->memory_type = type >> MEMORY_TYPE_SHIFT;
460 	return set_memory_type(area->id, physicalBase, area->size, type);
461 }
462