xref: /haiku/src/system/boot/platform/openfirmware/arch/sparc/mmu.cpp (revision 4c8e85b316c35a9161f5a1c50ad70bc91c83a76f)
1 /*
2  * Copyright 2003-2009, Axel Dörfler, axeld@pinc-software.de.
3  * Copyright 2010-2011, Haiku, Inc. All Rights Reserved.
4  * All rights reserved. Distributed under the terms of the MIT License.
5  *
6  * Authors:
7  *		Axel Dörfler, axeld@pinc-software.de.
8  *		Alexander von Gluck, kallisti5@unixzen.com
9  */
10 
11 
12 #include <OS.h>
13 
14 #include <platform_arch.h>
15 #include <boot/addr_range.h>
16 #include <boot/kernel_args.h>
17 #include <boot/platform.h>
18 #include <boot/stage2.h>
19 #include <boot/stdio.h>
20 #include <platform/openfirmware/openfirmware.h>
21 #include <arch_cpu.h>
22 #include <arch_mmu.h>
23 #include <kernel.h>
24 
25 #include "support.h"
26 
27 
28 #define PAGE_READ_ONLY	0x0002
29 #define PAGE_READ_WRITE	0x0001
30 
31 // NULL is actually a possible physical address, so use -1 (which is
32 // misaligned, so not a valid address) as the invalid physical address.
33 #define PHYSINVAL ((void *)-1)
34 //#define PHYSINVAL NULL
35 
36 //#define TRACE_MMU
37 #ifdef TRACE_MMU
38 #   define TRACE(x...) dprintf(x)
39 #else
40 #   define TRACE(x...) ;
41 #endif
42 
43 
44 unsigned int sMmuInstance;
45 unsigned int sMemoryInstance;
46 
47 
48 // begin and end of the boot loader
49 extern "C" uint8 __text_begin;
50 extern "C" uint8 _end;
51 
52 
53 static status_t
54 insert_virtual_range_to_keep(void *start, uint32 size)
55 {
56 	return insert_address_range(gKernelArgs.arch_args.virtual_ranges_to_keep,
57 		&gKernelArgs.arch_args.num_virtual_ranges_to_keep,
58 		MAX_VIRTUAL_RANGES_TO_KEEP, (addr_t)start, size);
59 }
60 
61 
62 static status_t
63 remove_virtual_range_to_keep(void *start, uint32 size)
64 {
65 	return remove_address_range(gKernelArgs.arch_args.virtual_ranges_to_keep,
66 		&gKernelArgs.arch_args.num_virtual_ranges_to_keep,
67 		MAX_VIRTUAL_RANGES_TO_KEEP, (addr_t)start, size);
68 }
69 
70 
71 static status_t
72 find_physical_memory_ranges(size_t &total)
73 {
74 	TRACE("checking for memory...\n");
75 	intptr_t package = of_instance_to_package(sMemoryInstance);
76 
77 	total = 0;
78 
79 	// Memory base addresses are provided in 32 or 64 bit flavors
80 	// #address-cells and #size-cells matches the number of 32-bit 'cells'
81 	// representing the length of the base address and size fields
82 	intptr_t root = of_finddevice("/");
83 	int32 regSizeCells = of_size_cells(root);
84 	if (regSizeCells == OF_FAILED) {
85 		dprintf("finding size of memory cells failed, assume 32-bit.\n");
86 		regSizeCells = 1;
87 	}
88 
89 	int32 regAddressCells = of_address_cells(root);
90 	if (regAddressCells == OF_FAILED) {
91 		// Sun Netra T1-105 is missing this, but we can guess that if the size
92 		// is 64bit, the address also likely is.
93 		regAddressCells = regSizeCells;
94 	}
95 
96 	if (regAddressCells != 2 || regSizeCells != 2) {
97 		panic("%s: Unsupported OpenFirmware cell count detected.\n"
98 		"Address Cells: %" B_PRId32 "; Size Cells: %" B_PRId32
99 		" (CPU > 64bit?).\n", __func__, regAddressCells, regSizeCells);
100 		return B_ERROR;
101 	}
102 
103 	static struct of_region<uint64, uint64> regions[64];
104 	int count = of_getprop(package, "reg", regions, sizeof(regions));
105 	if (count == OF_FAILED)
106 		count = of_getprop(sMemoryInstance, "reg", regions, sizeof(regions));
107 	if (count == OF_FAILED)
108 		return B_ERROR;
109 	count /= sizeof(regions[0]);
110 
111 	for (int32 i = 0; i < count; i++) {
112 		if (regions[i].size <= 0) {
113 			TRACE("%d: empty region\n", i);
114 			continue;
115 		}
116 		TRACE("%" B_PRIu32 ": base = %" B_PRIx64 ","
117 			"size = %" B_PRIx64 "\n", i, regions[i].base, regions[i].size);
118 
119 		total += regions[i].size;
120 
121 		if (insert_physical_memory_range((addr_t)regions[i].base,
122 				regions[i].size) != B_OK) {
123 			dprintf("cannot map physical memory range "
124 				"(num ranges = %" B_PRIu32 ")!\n",
125 				gKernelArgs.num_physical_memory_ranges);
126 			return B_ERROR;
127 		}
128 	}
129 
130 	return B_OK;
131 }
132 
133 
134 static bool
135 is_virtual_allocated(void *address, size_t size)
136 {
137 	uint64 foundBase;
138 	return !get_free_address_range(gKernelArgs.virtual_allocated_range,
139 		gKernelArgs.num_virtual_allocated_ranges, (addr_t)address, size,
140 		&foundBase) || foundBase != (addr_t)address;
141 }
142 
143 
144 static bool
145 is_physical_allocated(void *address, size_t size)
146 {
147 	uint64 foundBase;
148 	return !get_free_address_range(gKernelArgs.physical_allocated_range,
149 		gKernelArgs.num_physical_allocated_ranges, (addr_t)address, size,
150 		&foundBase) || foundBase != (addr_t)address;
151 }
152 
153 
154 static bool
155 is_physical_memory(void *address, size_t size = 1)
156 {
157 	return is_address_range_covered(gKernelArgs.physical_memory_range,
158 		gKernelArgs.num_physical_memory_ranges, (addr_t)address, size);
159 }
160 
161 
162 static bool
163 map_range(void *virtualAddress, void *physicalAddress, size_t size, uint16 mode)
164 {
165 	// everything went fine, so lets mark the space as used.
166 	int status = of_call_method(sMmuInstance, "map", 5, 0, (uint64)mode, size,
167 		virtualAddress, 0, physicalAddress);
168 
169 	if (status != 0) {
170 		dprintf("map_range(base: %p, size: %" B_PRIuSIZE ") "
171 			"mapping failed\n", virtualAddress, size);
172 		return false;
173 	}
174 
175 	return true;
176 }
177 
178 
179 static status_t
180 find_allocated_ranges(void **_exceptionHandlers)
181 {
182 	// we have to preserve the OpenFirmware established mappings
183 	// if we want to continue to use its service after we've
184 	// taken over (we will probably need less translations once
185 	// we have proper driver support for the target hardware).
186 	intptr_t mmu = of_instance_to_package(sMmuInstance);
187 
188 	static struct translation_map {
189 		void *PhysicalAddress() {
190 			int64_t p = data;
191 #if 0
192 			// The openboot own "map?" word does not do this, so it must not
193 			// be needed
194 			// Sign extend
195 			p <<= 23;
196 			p >>= 23;
197 #endif
198 
199 			// Keep only PA[40:13]
200 			// FIXME later CPUs have some more bits here
201 			p &= 0x000001FFFFFFE000ll;
202 
203 			return (void*)p;
204 		}
205 
206 		int16_t Mode() {
207 			int16_t mode;
208 			if (data & 2)
209 				mode = PAGE_READ_WRITE;
210 			else
211 				mode = PAGE_READ_ONLY;
212 			return mode;
213 		}
214 
215 		void	*virtual_address;
216 		intptr_t length;
217 		intptr_t data;
218 	} translations[64];
219 
220 	int length = of_getprop(mmu, "translations", &translations,
221 		sizeof(translations));
222 	if (length == OF_FAILED) {
223 		dprintf("Error: no OF translations.\n");
224 		return B_ERROR;
225 	}
226 	length = length / sizeof(struct translation_map);
227 	uint32 total = 0;
228 	TRACE("found %d translations\n", length);
229 
230 	for (int i = 0; i < length; i++) {
231 		struct translation_map *map = &translations[i];
232 		bool keepRange = true;
233 		TRACE("%i: map: %p, length %ld -> phy %p mode %d: ", i,
234 			map->virtual_address, map->length,
235 			map->PhysicalAddress(), map->Mode());
236 
237 		// insert range in physical allocated, if it points to physical memory
238 
239 		if (is_physical_memory(map->PhysicalAddress())
240 			&& insert_physical_allocated_range((addr_t)map->PhysicalAddress(),
241 				map->length) != B_OK) {
242 			dprintf("cannot map physical allocated range "
243 				"(num ranges = %" B_PRIu32 ")!\n",
244 				gKernelArgs.num_physical_allocated_ranges);
245 			return B_ERROR;
246 		}
247 
248 		// insert range in virtual allocated
249 
250 		if (insert_virtual_allocated_range((addr_t)map->virtual_address,
251 				map->length) != B_OK) {
252 			dprintf("cannot map virtual allocated range "
253 				"(num ranges = %" B_PRIu32 ")!\n",
254 				gKernelArgs.num_virtual_allocated_ranges);
255 		}
256 
257 		// insert range in virtual ranges to keep
258 
259 		if (keepRange) {
260 			TRACE("keeping\n");
261 
262 			if (insert_virtual_range_to_keep(map->virtual_address,
263 					map->length) != B_OK) {
264 				dprintf("cannot map virtual range to keep "
265 					"(num ranges = %" B_PRIu32 ")\n",
266 					gKernelArgs.num_virtual_allocated_ranges);
267 			}
268 		} else {
269 			TRACE("dropping\n");
270 		}
271 
272 		total += map->length;
273 	}
274 	TRACE("total size kept: %" B_PRIu32 "\n", total);
275 
276 	// remove the boot loader code from the virtual ranges to keep in the
277 	// kernel
278 	if (remove_virtual_range_to_keep(&__text_begin, &_end - &__text_begin)
279 			!= B_OK) {
280 		dprintf("%s: Failed to remove boot loader range "
281 			"from virtual ranges to keep.\n", __func__);
282 	}
283 
284 	return B_OK;
285 }
286 
287 
288 static void *
289 find_physical_memory_range(size_t size)
290 {
291 	for (uint32 i = 0; i < gKernelArgs.num_physical_memory_ranges; i++) {
292 		if (gKernelArgs.physical_memory_range[i].size > size)
293 			return (void *)(addr_t)gKernelArgs.physical_memory_range[i].start;
294 	}
295 	return PHYSINVAL;
296 }
297 
298 
299 static void *
300 find_free_physical_range(size_t size)
301 {
302 	// If nothing is allocated, just return the first address in RAM
303 	if (gKernelArgs.num_physical_allocated_ranges == 0) {
304 		if (gKernelArgs.num_physical_memory_ranges == 0)
305 			return PHYSINVAL;
306 
307 		return find_physical_memory_range(size);
308 	}
309 
310 	// Try to find space after an already allocated range
311 	for (uint32 i = 0; i < gKernelArgs.num_physical_allocated_ranges; i++) {
312 		void *address
313 			= (void *)(addr_t)(gKernelArgs.physical_allocated_range[i].start
314 				+ gKernelArgs.physical_allocated_range[i].size);
315 		if (!is_physical_allocated(address, size)
316 			&& is_physical_memory(address, size)) {
317 			return address;
318 		}
319 	}
320 
321 	// Check if there is enough space at the start of one of the physical ranges
322 	// (that memory isn't after an already allocated range so it wouldn't be
323 	// found by the method above for ranges where there isn't already an initial
324 	// allocation at the start)
325 	for (uint32 i = 0; i < gKernelArgs.num_physical_memory_ranges; i++) {
326 		void *address = (void *)gKernelArgs.physical_memory_range[i].start;
327 		if (gKernelArgs.physical_memory_range[i].size > size
328 			&& !is_physical_allocated(address, size)) {
329 			return address;
330 		}
331 	}
332 
333 	// We're really out of memory
334 	return PHYSINVAL;
335 }
336 
337 
338 static void *
339 find_free_virtual_range(void *base, size_t size)
340 {
341 	if (base && !is_virtual_allocated(base, size))
342 		return base;
343 
344 	void *firstFound = NULL;
345 	void *firstBaseFound = NULL;
346 	for (uint32 i = 0; i < gKernelArgs.num_virtual_allocated_ranges; i++) {
347 		void *address
348 			= (void *)(addr_t)(gKernelArgs.virtual_allocated_range[i].start
349 				+ gKernelArgs.virtual_allocated_range[i].size);
350 		if (!is_virtual_allocated(address, size)) {
351 			if (!base)
352 				return address;
353 
354 			if (firstFound == NULL)
355 				firstFound = address;
356 			if (address >= base
357 				&& (firstBaseFound == NULL || address < firstBaseFound)) {
358 				firstBaseFound = address;
359 			}
360 		}
361 	}
362 	return (firstBaseFound ? firstBaseFound : firstFound);
363 }
364 
365 
366 extern "C" void *
367 arch_mmu_allocate(void *_virtualAddress, size_t size, uint8 _protection,
368 	bool exactAddress)
369 {
370 	// we only know page sizes
371 	size = ROUNDUP(size, B_PAGE_SIZE);
372 
373 	uint8 protection = 0;
374 	if (_protection & B_WRITE_AREA)
375 		protection = PAGE_READ_WRITE;
376 	else
377 		protection = PAGE_READ_ONLY;
378 
379 	// If no address is given, use the KERNEL_BASE as base address, since
380 	// that avoids trouble in the kernel, when we decide to keep the region.
381 	void *virtualAddress = _virtualAddress;
382 #if 0
383 	if (!virtualAddress)
384 		virtualAddress = (void*)KERNEL_BASE;
385 #endif
386 
387 	// find free address large enough to hold "size"
388 	virtualAddress = find_free_virtual_range(virtualAddress, size);
389 	if (virtualAddress == NULL)
390 		return NULL;
391 
392 	// fail if the exact address was requested, but is not free
393 	if (exactAddress && _virtualAddress && virtualAddress != _virtualAddress) {
394 		dprintf("arch_mmu_allocate(): exact address requested, but virtual "
395 			"range (base: %p, size: %" B_PRIuSIZE ") is not free.\n",
396 			_virtualAddress, size);
397 		return NULL;
398 	}
399 
400 #if 0
401 	intptr_t status;
402 
403 	/* claim the address */
404 	status = of_call_method(sMmuInstance, "claim", 3, 1, 0, size,
405 		virtualAddress, &_virtualAddress);
406 	if (status != 0) {
407 		dprintf("arch_mmu_allocate(base: %p, size: %" B_PRIuSIZE ") "
408 			"failed to claim virtual address\n", virtualAddress, size);
409 		return NULL;
410 	}
411 
412 #endif
413 	// we have a free virtual range for the allocation, now
414 	// have a look for free physical memory as well (we assume
415 	// that a) there is enough memory, and b) failing is fatal
416 	// so that we don't have to optimize for these cases :)
417 
418 	void *physicalAddress = find_free_physical_range(size);
419 	if (physicalAddress == PHYSINVAL) {
420 		dprintf("arch_mmu_allocate(base: %p, size: %" B_PRIuSIZE ") "
421 			"no free physical address\n", virtualAddress, size);
422 		return NULL;
423 	}
424 
425 	// everything went fine, so lets mark the space as used.
426 
427 #if 0
428 	void* _physicalAddress;
429 	status = of_call_method(sMemoryInstance, "claim", 3, 1, physicalAddress,
430 		1, size, &_physicalAddress);
431 
432 	if (status != 0) {
433 		dprintf("arch_mmu_allocate(base: %p, size: %" B_PRIuSIZE ") "
434 			"failed to claim physical address\n", physicalAddress, size);
435 		return NULL;
436 	}
437 #endif
438 
439 	insert_virtual_allocated_range((addr_t)virtualAddress, size);
440 	insert_physical_allocated_range((addr_t)physicalAddress, size);
441 
442 	if (!map_range(virtualAddress, physicalAddress, size, protection))
443 		return NULL;
444 
445 	return virtualAddress;
446 }
447 
448 
449 extern "C" status_t
450 arch_mmu_free(void *address, size_t size)
451 {
452 	// TODO: implement freeing a region!
453 	return B_OK;
454 }
455 
456 
457 //	#pragma mark - OpenFirmware callbacks and public API
458 
459 
460 #if 0
461 static int
462 map_callback(struct of_arguments *args)
463 {
464 	void *physicalAddress = (void *)args->Argument(0);
465 	void *virtualAddress = (void *)args->Argument(1);
466 	int length = args->Argument(2);
467 	int mode = args->Argument(3);
468 	intptr_t &error = args->ReturnValue(0);
469 
470 	// insert range in physical allocated if needed
471 
472 	if (is_physical_memory(physicalAddress)
473 		&& insert_physical_allocated_range((addr_t)physicalAddress, length)
474 			!= B_OK) {
475 		error = -1;
476 		return OF_FAILED;
477 	}
478 
479 	// insert range in virtual allocated
480 
481 	if (insert_virtual_allocated_range((addr_t)virtualAddress, length)
482 			!= B_OK) {
483 		error = -2;
484 		return OF_FAILED;
485 	}
486 
487 	// map range into the page table
488 
489 	map_range(virtualAddress, physicalAddress, length, mode);
490 
491 	return B_OK;
492 }
493 
494 
495 static int
496 unmap_callback(struct of_arguments *args)
497 {
498 /*	void *address = (void *)args->Argument(0);
499 	int length = args->Argument(1);
500 	int &error = args->ReturnValue(0);
501 */
502 	// TODO: to be implemented
503 
504 	return OF_FAILED;
505 }
506 
507 
508 static int
509 translate_callback(struct of_arguments *args)
510 {
511 	// could not find the translation
512 	return OF_FAILED;
513 }
514 
515 
516 static int
517 alloc_real_mem_callback(struct of_arguments *args)
518 {
519 /*	addr_t minAddress = (addr_t)args->Argument(0);
520 	addr_t maxAddress = (addr_t)args->Argument(1);
521 	int length = args->Argument(2);
522 	int mode = args->Argument(3);
523 	int &error = args->ReturnValue(0);
524 	int &physicalAddress = args->ReturnValue(1);
525 */
526 	// ToDo: to be implemented
527 
528 	return OF_FAILED;
529 }
530 
531 
532 /** Dispatches the callback to the responsible function */
533 
534 static int
535 callback(struct of_arguments *args)
536 {
537 	const char *name = args->name;
538 	TRACE("OF CALLBACK: %s\n", name);
539 
540 	if (!strcmp(name, "map"))
541 		return map_callback(args);
542 	else if (!strcmp(name, "unmap"))
543 		return unmap_callback(args);
544 	else if (!strcmp(name, "translate"))
545 		return translate_callback(args);
546 	else if (!strcmp(name, "alloc-real-mem"))
547 		return alloc_real_mem_callback(args);
548 
549 	return OF_FAILED;
550 }
551 #endif
552 
553 
554 extern "C" status_t
555 arch_set_callback(void)
556 {
557 #if 0
558 	// set OpenFirmware callbacks - it will ask us for memory after that
559 	// instead of maintaining it itself
560 
561 	void *oldCallback = NULL;
562 	if (of_call_client_function("set-callback", 1, 1, &callback, &oldCallback)
563 			== OF_FAILED) {
564 		dprintf("Error: OpenFirmware set-callback failed\n");
565 		return B_ERROR;
566 	}
567 	TRACE("old callback = %p; new callback = %p\n", oldCallback, callback);
568 #endif
569 
570 	return B_OK;
571 }
572 
573 
574 extern "C" status_t
575 arch_mmu_init(void)
576 {
577 	if (of_getprop(gChosen, "mmu", &sMmuInstance, sizeof(int)) == OF_FAILED) {
578 		dprintf("%s: Error: no OpenFirmware mmu\n", __func__);
579 		return B_ERROR;
580 	}
581 
582 	if (of_getprop(gChosen, "memory", &sMemoryInstance, sizeof(int)) == OF_FAILED) {
583 		dprintf("%s: Error: no OpenFirmware memory\n", __func__);
584 		return B_ERROR;
585 	}
586 	// get map of physical memory (fill in kernel_args structure)
587 
588 	size_t total;
589 	if (find_physical_memory_ranges(total) != B_OK) {
590 		dprintf("Error: could not find physical memory ranges!\n");
591 		return B_ERROR;
592 	}
593 	TRACE("total physical memory = %luMB\n", total / (1024 * 1024));
594 
595 	void *exceptionHandlers = (void *)-1;
596 	if (find_allocated_ranges(&exceptionHandlers) != B_OK) {
597 		dprintf("Error: find_allocated_ranges() failed\n");
598 		return B_ERROR;
599 	}
600 
601 #if 0
602 	if (exceptionHandlers == (void *)-1) {
603 		// TODO: create mapping for the exception handlers
604 		dprintf("Error: no mapping for the exception handlers!\n");
605 	}
606 
607 	// Set the Open Firmware memory callback. From now on the Open Firmware
608 	// will ask us for memory.
609 	arch_set_callback();
610 
611 	// set up new page table and turn on translation again
612 	// TODO "set up new page table and turn on translation again" (see PPC)
613 #endif
614 
615 	// set kernel args
616 
617 	TRACE("virt_allocated: %" B_PRIu32 "\n",
618 		gKernelArgs.num_virtual_allocated_ranges);
619 	TRACE("phys_allocated: %" B_PRIu32 "\n",
620 		gKernelArgs.num_physical_allocated_ranges);
621 	TRACE("phys_memory: %" B_PRIu32 "\n",
622 		gKernelArgs.num_physical_memory_ranges);
623 
624 #if 0
625 	// TODO set gKernelArgs.arch_args content if we have something to put in there
626 	gKernelArgs.arch_args.page_table.start = (addr_t)sPageTable;
627 	gKernelArgs.arch_args.page_table.size = tableSize;
628 
629 	gKernelArgs.arch_args.exception_handlers.start = (addr_t)exceptionHandlers;
630 	gKernelArgs.arch_args.exception_handlers.size = B_PAGE_SIZE;
631 #endif
632 
633 	return B_OK;
634 }
635 
636