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