/* * Copyright 2003-2009, Axel Dörfler, axeld@pinc-software.de. * Copyright 2010-2011, Haiku, Inc. All Rights Reserved. * All rights reserved. Distributed under the terms of the MIT License. * * Authors: * Axel Dörfler, axeld@pinc-software.de. * Alexander von Gluck, kallisti5@unixzen.com */ #include #include #include #include #include #include #include #include #include #include #include #include "support.h" #define PAGE_READ_ONLY 0x0002 #define PAGE_READ_WRITE 0x0001 // NULL is actually a possible physical address, so use -1 (which is // misaligned, so not a valid address) as the invalid physical address. #define PHYSINVAL ((void *)-1) //#define PHYSINVAL NULL //#define TRACE_MMU #ifdef TRACE_MMU # define TRACE(x...) dprintf(x) #else # define TRACE(x...) ; #endif unsigned int sMmuInstance; unsigned int sMemoryInstance; // begin and end of the boot loader extern "C" uint8 __text_begin; extern "C" uint8 _end; static status_t insert_virtual_range_to_keep(void *start, uint32 size) { return insert_address_range(gKernelArgs.arch_args.virtual_ranges_to_keep, &gKernelArgs.arch_args.num_virtual_ranges_to_keep, MAX_VIRTUAL_RANGES_TO_KEEP, (addr_t)start, size); } static status_t remove_virtual_range_to_keep(void *start, uint32 size) { return remove_address_range(gKernelArgs.arch_args.virtual_ranges_to_keep, &gKernelArgs.arch_args.num_virtual_ranges_to_keep, MAX_VIRTUAL_RANGES_TO_KEEP, (addr_t)start, size); } static status_t find_physical_memory_ranges(size_t &total) { TRACE("checking for memory...\n"); intptr_t package = of_instance_to_package(sMemoryInstance); total = 0; // Memory base addresses are provided in 32 or 64 bit flavors // #address-cells and #size-cells matches the number of 32-bit 'cells' // representing the length of the base address and size fields intptr_t root = of_finddevice("/"); int32 regSizeCells = of_size_cells(root); if (regSizeCells == OF_FAILED) { dprintf("finding size of memory cells failed, assume 32-bit.\n"); regSizeCells = 1; } int32 regAddressCells = of_address_cells(root); if (regAddressCells == OF_FAILED) { // Sun Netra T1-105 is missing this, but we can guess that if the size // is 64bit, the address also likely is. regAddressCells = regSizeCells; } if (regAddressCells != 2 || regSizeCells != 2) { panic("%s: Unsupported OpenFirmware cell count detected.\n" "Address Cells: %" B_PRId32 "; Size Cells: %" B_PRId32 " (CPU > 64bit?).\n", __func__, regAddressCells, regSizeCells); return B_ERROR; } static struct of_region regions[64]; int count = of_getprop(package, "reg", regions, sizeof(regions)); if (count == OF_FAILED) count = of_getprop(sMemoryInstance, "reg", regions, sizeof(regions)); if (count == OF_FAILED) return B_ERROR; count /= sizeof(regions[0]); for (int32 i = 0; i < count; i++) { if (regions[i].size <= 0) { TRACE("%d: empty region\n", i); continue; } TRACE("%" B_PRIu32 ": base = %" B_PRIx64 "," "size = %" B_PRIx64 "\n", i, regions[i].base, regions[i].size); total += regions[i].size; if (insert_physical_memory_range((addr_t)regions[i].base, regions[i].size) != B_OK) { dprintf("cannot map physical memory range " "(num ranges = %" B_PRIu32 ")!\n", gKernelArgs.num_physical_memory_ranges); return B_ERROR; } } return B_OK; } static bool is_virtual_allocated(void *address, size_t size) { uint64 foundBase; return !get_free_address_range(gKernelArgs.virtual_allocated_range, gKernelArgs.num_virtual_allocated_ranges, (addr_t)address, size, &foundBase) || foundBase != (addr_t)address; } static bool is_physical_allocated(void *address, size_t size) { uint64 foundBase; return !get_free_address_range(gKernelArgs.physical_allocated_range, gKernelArgs.num_physical_allocated_ranges, (addr_t)address, size, &foundBase) || foundBase != (addr_t)address; } static bool is_physical_memory(void *address, size_t size = 1) { return is_address_range_covered(gKernelArgs.physical_memory_range, gKernelArgs.num_physical_memory_ranges, (addr_t)address, size); } static bool map_range(void *virtualAddress, void *physicalAddress, size_t size, uint16 mode) { // everything went fine, so lets mark the space as used. int status = of_call_method(sMmuInstance, "map", 5, 0, (uint64)mode, size, virtualAddress, 0, physicalAddress); if (status != 0) { dprintf("map_range(base: %p, size: %" B_PRIuSIZE ") " "mapping failed\n", virtualAddress, size); return false; } return true; } static status_t find_allocated_ranges(void **_exceptionHandlers) { // we have to preserve the OpenFirmware established mappings // if we want to continue to use its service after we've // taken over (we will probably need less translations once // we have proper driver support for the target hardware). intptr_t mmu = of_instance_to_package(sMmuInstance); static struct translation_map { void *PhysicalAddress() { int64_t p = data; #if 0 // The openboot own "map?" word does not do this, so it must not // be needed // Sign extend p <<= 23; p >>= 23; #endif // Keep only PA[40:13] // FIXME later CPUs have some more bits here p &= 0x000001FFFFFFE000ll; return (void*)p; } int16_t Mode() { int16_t mode; if (data & 2) mode = PAGE_READ_WRITE; else mode = PAGE_READ_ONLY; return mode; } void *virtual_address; intptr_t length; intptr_t data; } translations[64]; int length = of_getprop(mmu, "translations", &translations, sizeof(translations)); if (length == OF_FAILED) { dprintf("Error: no OF translations.\n"); return B_ERROR; } length = length / sizeof(struct translation_map); uint32 total = 0; TRACE("found %d translations\n", length); for (int i = 0; i < length; i++) { struct translation_map *map = &translations[i]; bool keepRange = true; TRACE("%i: map: %p, length %ld -> phy %p mode %d: ", i, map->virtual_address, map->length, map->PhysicalAddress(), map->Mode()); // insert range in physical allocated, if it points to physical memory if (is_physical_memory(map->PhysicalAddress()) && insert_physical_allocated_range((addr_t)map->PhysicalAddress(), map->length) != B_OK) { dprintf("cannot map physical allocated range " "(num ranges = %" B_PRIu32 ")!\n", gKernelArgs.num_physical_allocated_ranges); return B_ERROR; } // insert range in virtual allocated if (insert_virtual_allocated_range((addr_t)map->virtual_address, map->length) != B_OK) { dprintf("cannot map virtual allocated range " "(num ranges = %" B_PRIu32 ")!\n", gKernelArgs.num_virtual_allocated_ranges); } // insert range in virtual ranges to keep if (keepRange) { TRACE("keeping\n"); if (insert_virtual_range_to_keep(map->virtual_address, map->length) != B_OK) { dprintf("cannot map virtual range to keep " "(num ranges = %" B_PRIu32 ")\n", gKernelArgs.num_virtual_allocated_ranges); } } else { TRACE("dropping\n"); } total += map->length; } TRACE("total size kept: %" B_PRIu32 "\n", total); // remove the boot loader code from the virtual ranges to keep in the // kernel if (remove_virtual_range_to_keep(&__text_begin, &_end - &__text_begin) != B_OK) { dprintf("%s: Failed to remove boot loader range " "from virtual ranges to keep.\n", __func__); } return B_OK; } static void * find_physical_memory_range(size_t size) { for (uint32 i = 0; i < gKernelArgs.num_physical_memory_ranges; i++) { if (gKernelArgs.physical_memory_range[i].size > size) return (void *)(addr_t)gKernelArgs.physical_memory_range[i].start; } return PHYSINVAL; } static void * find_free_physical_range(size_t size) { // If nothing is allocated, just return the first address in RAM if (gKernelArgs.num_physical_allocated_ranges == 0) { if (gKernelArgs.num_physical_memory_ranges == 0) return PHYSINVAL; return find_physical_memory_range(size); } // Try to find space after an already allocated range for (uint32 i = 0; i < gKernelArgs.num_physical_allocated_ranges; i++) { void *address = (void *)(addr_t)(gKernelArgs.physical_allocated_range[i].start + gKernelArgs.physical_allocated_range[i].size); if (!is_physical_allocated(address, size) && is_physical_memory(address, size)) { return address; } } // Check if there is enough space at the start of one of the physical ranges // (that memory isn't after an already allocated range so it wouldn't be // found by the method above for ranges where there isn't already an initial // allocation at the start) for (uint32 i = 0; i < gKernelArgs.num_physical_memory_ranges; i++) { void *address = (void *)gKernelArgs.physical_memory_range[i].start; if (gKernelArgs.physical_memory_range[i].size > size && !is_physical_allocated(address, size)) { return address; } } // We're really out of memory return PHYSINVAL; } static void * find_free_virtual_range(void *base, size_t size) { if (base && !is_virtual_allocated(base, size)) return base; void *firstFound = NULL; void *firstBaseFound = NULL; for (uint32 i = 0; i < gKernelArgs.num_virtual_allocated_ranges; i++) { void *address = (void *)(addr_t)(gKernelArgs.virtual_allocated_range[i].start + gKernelArgs.virtual_allocated_range[i].size); if (!is_virtual_allocated(address, size)) { if (!base) return address; if (firstFound == NULL) firstFound = address; if (address >= base && (firstBaseFound == NULL || address < firstBaseFound)) { firstBaseFound = address; } } } return (firstBaseFound ? firstBaseFound : firstFound); } extern "C" void * arch_mmu_allocate(void *_virtualAddress, size_t size, uint8 _protection, bool exactAddress) { // we only know page sizes size = ROUNDUP(size, B_PAGE_SIZE); uint8 protection = 0; if (_protection & B_WRITE_AREA) protection = PAGE_READ_WRITE; else protection = PAGE_READ_ONLY; // If no address is given, use the KERNEL_BASE as base address, since // that avoids trouble in the kernel, when we decide to keep the region. void *virtualAddress = _virtualAddress; #if 0 if (!virtualAddress) virtualAddress = (void*)KERNEL_BASE; #endif // find free address large enough to hold "size" virtualAddress = find_free_virtual_range(virtualAddress, size); if (virtualAddress == NULL) return NULL; // fail if the exact address was requested, but is not free if (exactAddress && _virtualAddress && virtualAddress != _virtualAddress) { dprintf("arch_mmu_allocate(): exact address requested, but virtual " "range (base: %p, size: %" B_PRIuSIZE ") is not free.\n", _virtualAddress, size); return NULL; } #if 0 intptr_t status; /* claim the address */ status = of_call_method(sMmuInstance, "claim", 3, 1, 0, size, virtualAddress, &_virtualAddress); if (status != 0) { dprintf("arch_mmu_allocate(base: %p, size: %" B_PRIuSIZE ") " "failed to claim virtual address\n", virtualAddress, size); return NULL; } #endif // we have a free virtual range for the allocation, now // have a look for free physical memory as well (we assume // that a) there is enough memory, and b) failing is fatal // so that we don't have to optimize for these cases :) void *physicalAddress = find_free_physical_range(size); if (physicalAddress == PHYSINVAL) { dprintf("arch_mmu_allocate(base: %p, size: %" B_PRIuSIZE ") " "no free physical address\n", virtualAddress, size); return NULL; } // everything went fine, so lets mark the space as used. #if 0 void* _physicalAddress; status = of_call_method(sMemoryInstance, "claim", 3, 1, physicalAddress, 1, size, &_physicalAddress); if (status != 0) { dprintf("arch_mmu_allocate(base: %p, size: %" B_PRIuSIZE ") " "failed to claim physical address\n", physicalAddress, size); return NULL; } #endif insert_virtual_allocated_range((addr_t)virtualAddress, size); insert_physical_allocated_range((addr_t)physicalAddress, size); if (!map_range(virtualAddress, physicalAddress, size, protection)) return NULL; return virtualAddress; } extern "C" status_t arch_mmu_free(void *address, size_t size) { // TODO: implement freeing a region! return B_OK; } // #pragma mark - OpenFirmware callbacks and public API #if 0 static int map_callback(struct of_arguments *args) { void *physicalAddress = (void *)args->Argument(0); void *virtualAddress = (void *)args->Argument(1); int length = args->Argument(2); int mode = args->Argument(3); intptr_t &error = args->ReturnValue(0); // insert range in physical allocated if needed if (is_physical_memory(physicalAddress) && insert_physical_allocated_range((addr_t)physicalAddress, length) != B_OK) { error = -1; return OF_FAILED; } // insert range in virtual allocated if (insert_virtual_allocated_range((addr_t)virtualAddress, length) != B_OK) { error = -2; return OF_FAILED; } // map range into the page table map_range(virtualAddress, physicalAddress, length, mode); return B_OK; } static int unmap_callback(struct of_arguments *args) { /* void *address = (void *)args->Argument(0); int length = args->Argument(1); int &error = args->ReturnValue(0); */ // TODO: to be implemented return OF_FAILED; } static int translate_callback(struct of_arguments *args) { // could not find the translation return OF_FAILED; } static int alloc_real_mem_callback(struct of_arguments *args) { /* addr_t minAddress = (addr_t)args->Argument(0); addr_t maxAddress = (addr_t)args->Argument(1); int length = args->Argument(2); int mode = args->Argument(3); int &error = args->ReturnValue(0); int &physicalAddress = args->ReturnValue(1); */ // ToDo: to be implemented return OF_FAILED; } /** Dispatches the callback to the responsible function */ static int callback(struct of_arguments *args) { const char *name = args->name; TRACE("OF CALLBACK: %s\n", name); if (!strcmp(name, "map")) return map_callback(args); else if (!strcmp(name, "unmap")) return unmap_callback(args); else if (!strcmp(name, "translate")) return translate_callback(args); else if (!strcmp(name, "alloc-real-mem")) return alloc_real_mem_callback(args); return OF_FAILED; } #endif extern "C" status_t arch_set_callback(void) { #if 0 // set OpenFirmware callbacks - it will ask us for memory after that // instead of maintaining it itself void *oldCallback = NULL; if (of_call_client_function("set-callback", 1, 1, &callback, &oldCallback) == OF_FAILED) { dprintf("Error: OpenFirmware set-callback failed\n"); return B_ERROR; } TRACE("old callback = %p; new callback = %p\n", oldCallback, callback); #endif return B_OK; } extern "C" status_t arch_mmu_init(void) { if (of_getprop(gChosen, "mmu", &sMmuInstance, sizeof(int)) == OF_FAILED) { dprintf("%s: Error: no OpenFirmware mmu\n", __func__); return B_ERROR; } if (of_getprop(gChosen, "memory", &sMemoryInstance, sizeof(int)) == OF_FAILED) { dprintf("%s: Error: no OpenFirmware memory\n", __func__); return B_ERROR; } // get map of physical memory (fill in kernel_args structure) size_t total; if (find_physical_memory_ranges(total) != B_OK) { dprintf("Error: could not find physical memory ranges!\n"); return B_ERROR; } TRACE("total physical memory = %luMB\n", total / (1024 * 1024)); void *exceptionHandlers = (void *)-1; if (find_allocated_ranges(&exceptionHandlers) != B_OK) { dprintf("Error: find_allocated_ranges() failed\n"); return B_ERROR; } #if 0 if (exceptionHandlers == (void *)-1) { // TODO: create mapping for the exception handlers dprintf("Error: no mapping for the exception handlers!\n"); } // Set the Open Firmware memory callback. From now on the Open Firmware // will ask us for memory. arch_set_callback(); // set up new page table and turn on translation again // TODO "set up new page table and turn on translation again" (see PPC) #endif // set kernel args TRACE("virt_allocated: %" B_PRIu32 "\n", gKernelArgs.num_virtual_allocated_ranges); TRACE("phys_allocated: %" B_PRIu32 "\n", gKernelArgs.num_physical_allocated_ranges); TRACE("phys_memory: %" B_PRIu32 "\n", gKernelArgs.num_physical_memory_ranges); #if 0 // TODO set gKernelArgs.arch_args content if we have something to put in there gKernelArgs.arch_args.page_table.start = (addr_t)sPageTable; gKernelArgs.arch_args.page_table.size = tableSize; gKernelArgs.arch_args.exception_handlers.start = (addr_t)exceptionHandlers; gKernelArgs.arch_args.exception_handlers.size = B_PAGE_SIZE; #endif return B_OK; }