/* * Copyright 2002-2007, Axel Dörfler, axeld@pinc-software.de. All rights reserved. * Distributed under the terms of the MIT License. * * Copyright 2001-2002, Travis Geiselbrecht. All rights reserved. * Distributed under the terms of the NewOS License. */ #include "generic_vm_physical_page_mapper.h" #include #include #include #include #include #include #include //#define TRACE_VM_PHYSICAL_PAGE_MAPPER #ifdef TRACE_VM_PHYSICAL_PAGE_MAPPER # define TRACE(x) dprintf x #else # define TRACE(x) ; #endif #define DEBUG_IO_SPACE // data and structures used to represent physical pages mapped into iospace typedef struct paddr_chunk_descriptor { struct paddr_chunk_descriptor *next_q; // must remain first in structure, queue code uses it int32 ref_count; addr_t va; #ifdef DEBUG_IO_SPACE thread_id last_ref; #endif } paddr_chunk_desc; static paddr_chunk_desc *paddr_desc; // will be one per physical chunk static paddr_chunk_desc **virtual_pmappings; // will be one ptr per virtual chunk in iospace static int first_free_vmapping; static int num_virtual_chunks; static queue mapped_paddr_lru; static mutex sMutex; static sem_id sChunkAvailableSem; static int32 sChunkAvailableWaitingCounter; static generic_map_iospace_chunk_func sMapIOSpaceChunk; static addr_t sIOSpaceBase; static size_t sIOSpaceSize; static size_t sIOSpaceChunkSize; status_t generic_get_physical_page(addr_t pa, addr_t *va, uint32 flags) { int index; paddr_chunk_desc *replaced_pchunk; restart: mutex_lock(&sMutex); // see if the page is already mapped index = pa / sIOSpaceChunkSize; if (paddr_desc[index].va != 0) { if (paddr_desc[index].ref_count++ == 0) { // pull this descriptor out of the lru list queue_remove_item(&mapped_paddr_lru, &paddr_desc[index]); } *va = paddr_desc[index].va + pa % sIOSpaceChunkSize; mutex_unlock(&sMutex); return B_OK; } // map it if (first_free_vmapping < num_virtual_chunks) { // there's a free hole paddr_desc[index].va = first_free_vmapping * sIOSpaceChunkSize + sIOSpaceBase; *va = paddr_desc[index].va + pa % sIOSpaceChunkSize; virtual_pmappings[first_free_vmapping] = &paddr_desc[index]; paddr_desc[index].ref_count++; // push up the first_free_vmapping pointer for (; first_free_vmapping < num_virtual_chunks; first_free_vmapping++) { if (virtual_pmappings[first_free_vmapping] == NULL) break; } sMapIOSpaceChunk(paddr_desc[index].va, index * sIOSpaceChunkSize); mutex_unlock(&sMutex); return B_OK; } // replace an earlier mapping if (queue_peek(&mapped_paddr_lru) == NULL) { // no free slots available if (flags == PHYSICAL_PAGE_NO_WAIT) { // put back to the caller and let them handle this mutex_unlock(&sMutex); return B_NO_MEMORY; } else { sChunkAvailableWaitingCounter++; mutex_unlock(&sMutex); acquire_sem(sChunkAvailableSem); goto restart; } } replaced_pchunk = (paddr_chunk_desc*)queue_dequeue(&mapped_paddr_lru); paddr_desc[index].va = replaced_pchunk->va; replaced_pchunk->va = 0; *va = paddr_desc[index].va + pa % sIOSpaceChunkSize; paddr_desc[index].ref_count++; #ifdef DEBUG_IO_SPACE paddr_desc[index].last_ref = thread_get_current_thread_id(); #endif virtual_pmappings[(*va - sIOSpaceBase) / sIOSpaceChunkSize] = paddr_desc + index; sMapIOSpaceChunk(paddr_desc[index].va, index * sIOSpaceChunkSize); mutex_unlock(&sMutex); return B_OK; } status_t generic_put_physical_page(addr_t va) { paddr_chunk_desc *desc; if (va < sIOSpaceBase || va >= sIOSpaceBase + sIOSpaceSize) panic("someone called put_physical_page on an invalid va 0x%lx\n", va); va -= sIOSpaceBase; mutex_lock(&sMutex); desc = virtual_pmappings[va / sIOSpaceChunkSize]; if (desc == NULL) { mutex_unlock(&sMutex); panic("put_physical_page called on page at va 0x%lx which is not checked out\n", va); return B_ERROR; } if (--desc->ref_count == 0) { // put it on the mapped lru list queue_enqueue(&mapped_paddr_lru, desc); if (sChunkAvailableWaitingCounter > 0) { sChunkAvailableWaitingCounter--; release_sem_etc(sChunkAvailableSem, 1, B_DO_NOT_RESCHEDULE); } } if (desc->ref_count < 0) panic("generic_put_physical_page(): ref count < 0: %ld\n", desc->ref_count); mutex_unlock(&sMutex); return B_OK; } #ifdef DEBUG_IO_SPACE static int dump_iospace(int argc, char** argv) { if (argc < 2) { kprintf("usage: iospace \n"); return 0; } int32 i; if (strchr(argv[1], 'p')) { // physical address descriptors kprintf("I/O space physical descriptors (%p)\n", paddr_desc); int32 max = 1024; if (argc == 3) max = strtol(argv[2], NULL, 0); for (i = 0; i < max; i++) { kprintf("[%03lx %p %3ld %3ld] ", i, (void *)paddr_desc[i].va, paddr_desc[i].ref_count, paddr_desc[i].last_ref); if (i % 4 == 3) kprintf("\n"); } if (i % 4) kprintf("\n"); } if (strchr(argv[1], 'v')) { // virtual mappings kprintf("I/O space virtual chunk mappings (%p, first free: %d)\n", virtual_pmappings, first_free_vmapping); for (i = 0; i < num_virtual_chunks; i++) { kprintf("[%2ld. %03lx] ", i, (virtual_pmappings[i] - paddr_desc) / sizeof(paddr_desc[0])); if (i % 8 == 7) kprintf("\n"); } if (i % 8) kprintf("\n"); } if (strchr(argv[1], 'q')) { // unused queue kprintf("I/O space mapped queue:\n"); paddr_chunk_descriptor* descriptor = (paddr_chunk_descriptor *)queue_peek(&mapped_paddr_lru); i = 0; while (descriptor != NULL) { kprintf("[%03lx %p] ", (descriptor - paddr_desc) / sizeof(paddr_desc[0]), descriptor); if (i++ % 8 == 7) kprintf("\n"); descriptor = descriptor->next_q; } if (i % 8) kprintf("\n"); } return 0; } #endif // #pragma mark - status_t generic_vm_physical_page_mapper_init(kernel_args *args, generic_map_iospace_chunk_func mapIOSpaceChunk, addr_t *ioSpaceBase, size_t ioSpaceSize, size_t ioSpaceChunkSize) { TRACE(("generic_vm_physical_page_mapper_init: entry\n")); sMapIOSpaceChunk = mapIOSpaceChunk; sIOSpaceSize = ioSpaceSize; sIOSpaceChunkSize = ioSpaceChunkSize; // reserve virtual space for the IO space // We reserve (ioSpaceChunkSize - B_PAGE_SIZE) bytes more, so that we // can guarantee to align the base address to ioSpaceChunkSize. sIOSpaceBase = vm_allocate_early(args, sIOSpaceSize + ioSpaceChunkSize - B_PAGE_SIZE, 0, 0); if (sIOSpaceBase == 0) { panic("generic_vm_physical_page_mapper_init(): Failed to reserve IO " "space in virtual address space!"); return B_ERROR; } // align the base address to chunk size sIOSpaceBase = (sIOSpaceBase + ioSpaceChunkSize - 1) / ioSpaceChunkSize * ioSpaceChunkSize; *ioSpaceBase = sIOSpaceBase; // allocate some space to hold physical page mapping info paddr_desc = (paddr_chunk_desc *)vm_allocate_early(args, sizeof(paddr_chunk_desc) * 1024, ~0L, B_KERNEL_READ_AREA | B_KERNEL_WRITE_AREA); num_virtual_chunks = sIOSpaceSize / sIOSpaceChunkSize; virtual_pmappings = (paddr_chunk_desc **)vm_allocate_early(args, sizeof(paddr_chunk_desc *) * num_virtual_chunks, ~0L, B_KERNEL_READ_AREA | B_KERNEL_WRITE_AREA); TRACE(("paddr_desc %p, virtual_pmappings %p"/*", iospace_pgtables %p"*/"\n", paddr_desc, virtual_pmappings/*, iospace_pgtables*/)); // initialize our data structures memset(paddr_desc, 0, sizeof(paddr_chunk_desc) * 1024); memset(virtual_pmappings, 0, sizeof(paddr_chunk_desc *) * num_virtual_chunks); first_free_vmapping = 0; queue_init(&mapped_paddr_lru); mutex_init(&sMutex, "iospace_mutex"); sChunkAvailableSem = -1; TRACE(("generic_vm_physical_page_mapper_init: done\n")); return B_OK; } status_t generic_vm_physical_page_mapper_init_post_area(kernel_args *args) { void *temp; TRACE(("generic_vm_physical_page_mapper_init_post_area: entry\n")); temp = (void *)paddr_desc; create_area("physical_page_mapping_descriptors", &temp, B_EXACT_ADDRESS, ROUNDUP(sizeof(paddr_chunk_desc) * 1024, B_PAGE_SIZE), B_ALREADY_WIRED, B_KERNEL_READ_AREA | B_KERNEL_WRITE_AREA); temp = (void *)virtual_pmappings; create_area("iospace_virtual_chunk_descriptors", &temp, B_EXACT_ADDRESS, ROUNDUP(sizeof(paddr_chunk_desc *) * num_virtual_chunks, B_PAGE_SIZE), B_ALREADY_WIRED, B_KERNEL_READ_AREA | B_KERNEL_WRITE_AREA); TRACE(("generic_vm_physical_page_mapper_init_post_area: creating iospace\n")); temp = (void *)sIOSpaceBase; area_id ioSpaceArea = vm_create_null_area(vm_kernel_address_space_id(), "iospace", &temp, B_EXACT_ADDRESS, sIOSpaceSize); if (ioSpaceArea < 0) { panic("generic_vm_physical_page_mapper_init_post_area(): Failed to " "create null area for IO space!\n"); return ioSpaceArea; } TRACE(("generic_vm_physical_page_mapper_init_post_area: done\n")); #ifdef DEBUG_IO_SPACE add_debugger_command("iospace", &dump_iospace, "Shows info about the I/O space area."); #endif return B_OK; } status_t generic_vm_physical_page_mapper_init_post_sem(kernel_args *args) { sChunkAvailableSem = create_sem(1, "iospace chunk available"); return sChunkAvailableSem >= B_OK ? B_OK : sChunkAvailableSem; }