xref: /haiku/src/system/boot/platform/openfirmware/arch/sparc/mmu.cpp (revision 77fb9ca3e653f72d1d15c9f1a50c3d4287f680e0)
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 // set protection to WIMGNPP: -----PP
29 // PP:	00 - no access
30 //		01 - read only
31 //		10 - read/write
32 //		11 - read only
33 #define PAGE_READ_ONLY	0x01
34 #define PAGE_READ_WRITE	0x02
35 
36 // NULL is actually a possible physical address...
37 //#define PHYSINVAL ((void *)-1)
38 #define PHYSINVAL NULL
39 
40 //#define TRACE_MMU
41 #ifdef TRACE_MMU
42 #   define TRACE(x...) dprintf(x)
43 #else
44 #   define TRACE(x...) ;
45 #endif
46 
47 
48 uint32 sPageTableHashMask;
49 
50 
51 // begin and end of the boot loader
52 extern "C" uint8 __text_begin;
53 extern "C" uint8 _end;
54 
55 
56 static status_t
57 find_physical_memory_ranges(size_t &total)
58 {
59 	int memory;
60 	dprintf("checking for memory...\n");
61 	if (of_getprop(gChosen, "memory", &memory, sizeof(int)) == OF_FAILED)
62 		return B_ERROR;
63 	int package = of_instance_to_package(memory);
64 
65 	total = 0;
66 
67 	// Memory base addresses are provided in 32 or 64 bit flavors
68 	// #address-cells and #size-cells matches the number of 32-bit 'cells'
69 	// representing the length of the base address and size fields
70 	int root = of_finddevice("/");
71 	int32 regAddressCells = of_address_cells(root);
72 	int32 regSizeCells = of_size_cells(root);
73 	if (regAddressCells == OF_FAILED || regSizeCells == OF_FAILED) {
74 		dprintf("finding base/size length counts failed, assume 32-bit.\n");
75 		regAddressCells = 1;
76 		regSizeCells = 1;
77 	}
78 
79 	// NOTE : Size Cells of 2 is possible in theory... but I haven't seen it yet.
80 	if (regAddressCells > 2 || regSizeCells > 1) {
81 		panic("%s: Unsupported OpenFirmware cell count detected.\n"
82 		"Address Cells: %" B_PRId32 "; Size Cells: %" B_PRId32
83 		" (CPU > 64bit?).\n", __func__, regAddressCells, regSizeCells);
84 		return B_ERROR;
85 	}
86 
87 	// On 64-bit PowerPC systems (G5), our mem base range address is larger
88 	if (regAddressCells == 2) {
89 		struct of_region<uint64> regions[64];
90 		int count = of_getprop(package, "reg", regions, sizeof(regions));
91 		if (count == OF_FAILED)
92 			count = of_getprop(memory, "reg", regions, sizeof(regions));
93 		if (count == OF_FAILED)
94 			return B_ERROR;
95 		count /= sizeof(regions[0]);
96 
97 		for (int32 i = 0; i < count; i++) {
98 			if (regions[i].size <= 0) {
99 				dprintf("%d: empty region\n", i);
100 				continue;
101 			}
102 			dprintf("%" B_PRIu32 ": base = %" B_PRIu64 ","
103 				"size = %" B_PRIu32 "\n", i, regions[i].base, regions[i].size);
104 
105 			total += regions[i].size;
106 
107 			if (insert_physical_memory_range((addr_t)regions[i].base,
108 					regions[i].size) != B_OK) {
109 				dprintf("cannot map physical memory range "
110 					"(num ranges = %" B_PRIu32 ")!\n",
111 					gKernelArgs.num_physical_memory_ranges);
112 				return B_ERROR;
113 			}
114 		}
115 		return B_OK;
116 	}
117 
118 	// Otherwise, normal 32-bit PowerPC G3 or G4 have a smaller 32-bit one
119 	struct of_region<uint32> regions[64];
120 	int count = of_getprop(package, "reg", regions, sizeof(regions));
121 	if (count == OF_FAILED)
122 		count = of_getprop(memory, "reg", regions, sizeof(regions));
123 	if (count == OF_FAILED)
124 		return B_ERROR;
125 	count /= sizeof(regions[0]);
126 
127 	for (int32 i = 0; i < count; i++) {
128 		if (regions[i].size <= 0) {
129 			dprintf("%d: empty region\n", i);
130 			continue;
131 		}
132 		dprintf("%" B_PRIu32 ": base = %" B_PRIu32 ","
133 			"size = %" B_PRIu32 "\n", i, regions[i].base, regions[i].size);
134 
135 		total += regions[i].size;
136 
137 		if (insert_physical_memory_range((addr_t)regions[i].base,
138 				regions[i].size) != B_OK) {
139 			dprintf("cannot map physical memory range "
140 				"(num ranges = %" B_PRIu32 ")!\n",
141 				gKernelArgs.num_physical_memory_ranges);
142 			return B_ERROR;
143 		}
144 	}
145 
146 	return B_OK;
147 }
148 
149 
150 static bool
151 is_virtual_allocated(void *address, size_t size)
152 {
153 	uint64 foundBase;
154 	return !get_free_address_range(gKernelArgs.virtual_allocated_range,
155 		gKernelArgs.num_virtual_allocated_ranges, (addr_t)address, size,
156 		&foundBase) || foundBase != (addr_t)address;
157 }
158 
159 
160 static bool
161 is_physical_allocated(void *address, size_t size)
162 {
163 	uint64 foundBase;
164 	return !get_free_address_range(gKernelArgs.physical_allocated_range,
165 		gKernelArgs.num_physical_allocated_ranges, (addr_t)address, size,
166 		&foundBase) || foundBase != (addr_t)address;
167 }
168 
169 
170 static bool
171 is_physical_memory(void *address, size_t size)
172 {
173 	return is_address_range_covered(gKernelArgs.physical_memory_range,
174 		gKernelArgs.num_physical_memory_ranges, (addr_t)address, size);
175 }
176 
177 
178 static bool
179 is_physical_memory(void *address)
180 {
181 	return is_physical_memory(address, 1);
182 }
183 
184 
185 static void
186 map_page(void *virtualAddress, void *physicalAddress, uint8 mode)
187 {
188 	panic("%s: out of page table entries!\n", __func__);
189 }
190 
191 
192 static void
193 map_range(void *virtualAddress, void *physicalAddress, size_t size, uint8 mode)
194 {
195 	for (uint32 offset = 0; offset < size; offset += B_PAGE_SIZE) {
196 		map_page((void *)(intptr_t(virtualAddress) + offset),
197 			(void *)(intptr_t(physicalAddress) + offset), mode);
198 	}
199 }
200 
201 
202 static void *
203 find_physical_memory_range(size_t size)
204 {
205 	for (uint32 i = 0; i < gKernelArgs.num_physical_memory_ranges; i++) {
206 		if (gKernelArgs.physical_memory_range[i].size > size)
207 			return (void *)(addr_t)gKernelArgs.physical_memory_range[i].start;
208 	}
209 	return PHYSINVAL;
210 }
211 
212 
213 static void *
214 find_free_physical_range(size_t size)
215 {
216 	// just do a simple linear search at the end of the allocated
217 	// ranges (dumb memory allocation)
218 	if (gKernelArgs.num_physical_allocated_ranges == 0) {
219 		if (gKernelArgs.num_physical_memory_ranges == 0)
220 			return PHYSINVAL;
221 
222 		return find_physical_memory_range(size);
223 	}
224 
225 	for (uint32 i = 0; i < gKernelArgs.num_physical_allocated_ranges; i++) {
226 		void *address
227 			= (void *)(addr_t)(gKernelArgs.physical_allocated_range[i].start
228 				+ gKernelArgs.physical_allocated_range[i].size);
229 		if (!is_physical_allocated(address, size)
230 			&& is_physical_memory(address, size))
231 			return address;
232 	}
233 	return PHYSINVAL;
234 }
235 
236 
237 static void *
238 find_free_virtual_range(void *base, size_t size)
239 {
240 	if (base && !is_virtual_allocated(base, size))
241 		return base;
242 
243 	void *firstFound = NULL;
244 	void *firstBaseFound = NULL;
245 	for (uint32 i = 0; i < gKernelArgs.num_virtual_allocated_ranges; i++) {
246 		void *address
247 			= (void *)(addr_t)(gKernelArgs.virtual_allocated_range[i].start
248 				+ gKernelArgs.virtual_allocated_range[i].size);
249 		if (!is_virtual_allocated(address, size)) {
250 			if (!base)
251 				return address;
252 
253 			if (firstFound == NULL)
254 				firstFound = address;
255 			if (address >= base
256 				&& (firstBaseFound == NULL || address < firstBaseFound)) {
257 				firstBaseFound = address;
258 			}
259 		}
260 	}
261 	return (firstBaseFound ? firstBaseFound : firstFound);
262 }
263 
264 
265 extern "C" void *
266 arch_mmu_allocate(void *_virtualAddress, size_t size, uint8 _protection,
267 	bool exactAddress)
268 {
269 	// we only know page sizes
270 	size = ROUNDUP(size, B_PAGE_SIZE);
271 
272 	uint8 protection = 0;
273 	if (_protection & B_WRITE_AREA)
274 		protection = PAGE_READ_WRITE;
275 	else
276 		protection = PAGE_READ_ONLY;
277 
278 	// If no address is given, use the KERNEL_BASE as base address, since
279 	// that avoids trouble in the kernel, when we decide to keep the region.
280 	void *virtualAddress = _virtualAddress;
281 	if (!virtualAddress)
282 		virtualAddress = (void*)KERNEL_BASE;
283 
284 	// find free address large enough to hold "size"
285 	virtualAddress = find_free_virtual_range(virtualAddress, size);
286 	if (virtualAddress == NULL)
287 		return NULL;
288 
289 	// fail if the exact address was requested, but is not free
290 	if (exactAddress && _virtualAddress && virtualAddress != _virtualAddress) {
291 		dprintf("arch_mmu_allocate(): exact address requested, but virtual "
292 			"range (base: %p, size: %" B_PRIuSIZE ") is not free.\n",
293 			_virtualAddress, size);
294 		return NULL;
295 	}
296 
297 	// we have a free virtual range for the allocation, now
298 	// have a look for free physical memory as well (we assume
299 	// that a) there is enough memory, and b) failing is fatal
300 	// so that we don't have to optimize for these cases :)
301 
302 	void *physicalAddress = find_free_physical_range(size);
303 	if (physicalAddress == PHYSINVAL) {
304 		dprintf("arch_mmu_allocate(base: %p, size: %" B_PRIuSIZE ") "
305 			"no free physical address\n", virtualAddress, size);
306 		return NULL;
307 	}
308 
309 	// everything went fine, so lets mark the space as used.
310 
311 	dprintf("mmu_alloc: va %p, pa %p, size %" B_PRIuSIZE "\n", virtualAddress,
312 		physicalAddress, size);
313 	insert_virtual_allocated_range((addr_t)virtualAddress, size);
314 	insert_physical_allocated_range((addr_t)physicalAddress, size);
315 
316 	map_range(virtualAddress, physicalAddress, size, protection);
317 
318 	return virtualAddress;
319 }
320 
321 
322 extern "C" status_t
323 arch_mmu_free(void *address, size_t size)
324 {
325 	// TODO: implement freeing a region!
326 	return B_OK;
327 }
328 
329 
330 //	#pragma mark - OpenFirmware callbacks and public API
331 
332 
333 static int
334 map_callback(struct of_arguments *args)
335 {
336 	void *physicalAddress = (void *)args->Argument(0);
337 	void *virtualAddress = (void *)args->Argument(1);
338 	int length = args->Argument(2);
339 	int mode = args->Argument(3);
340 	intptr_t &error = args->ReturnValue(0);
341 
342 	// insert range in physical allocated if needed
343 
344 	if (is_physical_memory(physicalAddress)
345 		&& insert_physical_allocated_range((addr_t)physicalAddress, length)
346 			!= B_OK) {
347 		error = -1;
348 		return OF_FAILED;
349 	}
350 
351 	// insert range in virtual allocated
352 
353 	if (insert_virtual_allocated_range((addr_t)virtualAddress, length)
354 			!= B_OK) {
355 		error = -2;
356 		return OF_FAILED;
357 	}
358 
359 	// map range into the page table
360 
361 	map_range(virtualAddress, physicalAddress, length, mode);
362 
363 	return B_OK;
364 }
365 
366 
367 static int
368 unmap_callback(struct of_arguments *args)
369 {
370 /*	void *address = (void *)args->Argument(0);
371 	int length = args->Argument(1);
372 	int &error = args->ReturnValue(0);
373 */
374 	// TODO: to be implemented
375 
376 	return OF_FAILED;
377 }
378 
379 
380 static int
381 translate_callback(struct of_arguments *args)
382 {
383 	// could not find the translation
384 	return OF_FAILED;
385 }
386 
387 
388 static int
389 alloc_real_mem_callback(struct of_arguments *args)
390 {
391 /*	addr_t minAddress = (addr_t)args->Argument(0);
392 	addr_t maxAddress = (addr_t)args->Argument(1);
393 	int length = args->Argument(2);
394 	int mode = args->Argument(3);
395 	int &error = args->ReturnValue(0);
396 	int &physicalAddress = args->ReturnValue(1);
397 */
398 	// ToDo: to be implemented
399 
400 	return OF_FAILED;
401 }
402 
403 
404 /** Dispatches the callback to the responsible function */
405 
406 static int
407 callback(struct of_arguments *args)
408 {
409 	const char *name = args->name;
410 	TRACE("OF CALLBACK: %s\n", name);
411 
412 	if (!strcmp(name, "map"))
413 		return map_callback(args);
414 	else if (!strcmp(name, "unmap"))
415 		return unmap_callback(args);
416 	else if (!strcmp(name, "translate"))
417 		return translate_callback(args);
418 	else if (!strcmp(name, "alloc-real-mem"))
419 		return alloc_real_mem_callback(args);
420 
421 	return OF_FAILED;
422 }
423 
424 
425 extern "C" status_t
426 arch_set_callback(void)
427 {
428 	// set OpenFirmware callbacks - it will ask us for memory after that
429 	// instead of maintaining it itself
430 
431 	void *oldCallback = NULL;
432 	if (of_call_client_function("set-callback", 1, 1, &callback, &oldCallback)
433 			== OF_FAILED) {
434 		dprintf("Error: OpenFirmware set-callback failed\n");
435 		return B_ERROR;
436 	}
437 	TRACE("old callback = %p; new callback = %p\n", oldCallback, callback);
438 
439 	return B_OK;
440 }
441 
442 
443 extern "C" status_t
444 arch_mmu_init(void)
445 {
446 	// get map of physical memory (fill in kernel_args structure)
447 
448 	size_t total;
449 	if (find_physical_memory_ranges(total) != B_OK) {
450 		dprintf("Error: could not find physical memory ranges!\n");
451 		return B_ERROR;
452 	}
453 	dprintf("total physical memory = %luMB\n", total / (1024 * 1024));
454 
455 	return B_OK;
456 }
457 
458