| /* |
| * Copyright (c) 2015 Google, Inc. All rights reserved |
| * |
| * Permission is hereby granted, free of charge, to any person obtaining |
| * a copy of this software and associated documentation files |
| * (the "Software"), to deal in the Software without restriction, |
| * including without limitation the rights to use, copy, modify, merge, |
| * publish, distribute, sublicense, and/or sell copies of the Software, |
| * and to permit persons to whom the Software is furnished to do so, |
| * subject to the following conditions: |
| * |
| * The above copyright notice and this permission notice shall be |
| * included in all copies or substantial portions of the Software. |
| * |
| * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
| * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
| * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. |
| * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY |
| * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, |
| * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE |
| * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
| */ |
| |
| #include "kernel/novm.h" |
| |
| #include <err.h> |
| #include <assert.h> |
| #include <trace.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <lk/init.h> |
| #include <kernel/mutex.h> |
| |
| #define LOCAL_TRACE 0 |
| |
| struct novm_arena { |
| mutex_t lock; |
| const char *name; |
| size_t pages; |
| char *map; |
| char *base; |
| size_t size; |
| |
| // We divide the memory up into pages. If there is memory we can use before |
| // the first aligned page address, then we record it here and the heap will use |
| // it. |
| #define MINIMUM_USEFUL_UNALIGNED_SIZE 64 |
| void *unaligned_area; |
| size_t unaligned_size; |
| }; |
| |
| |
| /* not a static vm, not using the kernel vm */ |
| extern int _end; |
| extern int _end_of_ram; |
| |
| #define MEM_START ((uintptr_t)&_end) |
| #define MEM_SIZE ((MEMBASE + MEMSIZE) - MEM_START) |
| #define DEFAULT_MAP_SIZE (MEMSIZE >> PAGE_SIZE_SHIFT) |
| |
| /* a static list of arenas */ |
| #ifndef NOVM_MAX_ARENAS |
| #define NOVM_MAX_ARENAS 1 |
| #endif |
| struct novm_arena arena[NOVM_MAX_ARENAS]; |
| |
| int novm_get_arenas(struct page_range* ranges, int number_of_ranges) |
| { |
| int ranges_found = 0; |
| for (int i = 0; i < number_of_ranges && i < NOVM_MAX_ARENAS; i++) { |
| if (arena[i].pages > 0) ranges_found = i + 1; |
| ranges[i].address = (void*)arena[i].base; |
| ranges[i].size = arena[i].pages << PAGE_SIZE_SHIFT; |
| } |
| return ranges_found; |
| } |
| |
| void *novm_alloc_unaligned(size_t *size_return) |
| { |
| /* only do the unaligned thing in the first arena */ |
| if (arena[0].unaligned_area != NULL) { |
| *size_return = arena[0].unaligned_size; |
| void *result = arena[0].unaligned_area; |
| arena[0].unaligned_area = NULL; |
| arena[0].unaligned_size = 0; |
| return result; |
| } |
| *size_return = PAGE_SIZE; |
| return novm_alloc_pages(1, NOVM_ARENA_ANY); |
| } |
| |
| static bool in_arena(struct novm_arena *n, void *p) |
| { |
| if (n->size == 0) |
| return false; |
| |
| char *ptr = (char *)p; |
| char *base = n->base; |
| return ptr >= base && ptr < base + n->size; |
| } |
| |
| static void novm_init_helper(struct novm_arena *n, const char *name, |
| uintptr_t arena_start, uintptr_t arena_size, |
| char *default_map, size_t default_map_size) |
| { |
| uintptr_t start = round_up(arena_start, PAGE_SIZE); |
| uintptr_t size = round_down(arena_start + arena_size, PAGE_SIZE) - start; |
| |
| mutex_init(&n->lock); |
| |
| size_t map_size = size >> PAGE_SIZE_SHIFT; |
| char *map = default_map; |
| if (map == NULL || default_map_size < map_size) { |
| // allocate the map out of the arena itself |
| map = (char *)arena_start; |
| |
| // Grab enough map for 16Mbyte of arena each time around the loop. |
| while (start - arena_start < map_size) { |
| start += PAGE_SIZE; |
| size -= PAGE_SIZE; |
| map_size--; |
| } |
| |
| if ((char *)start - (map + round_up(map_size, 4)) >= MINIMUM_USEFUL_UNALIGNED_SIZE) { |
| n->unaligned_area = map + round_up(map_size, 4); |
| n->unaligned_size = (char *)start - (map + round_up(map_size, 4)); |
| } |
| } else if (start - arena_start >= MINIMUM_USEFUL_UNALIGNED_SIZE) { |
| n->unaligned_area = (char *)arena_start; |
| n->unaligned_size = start - arena_start; |
| } |
| n->name = name; |
| n->map = map; |
| memset(n->map, 0, map_size); |
| n->pages = map_size; |
| n->base = (char *)start; |
| n->size = size; |
| } |
| |
| void novm_add_arena(const char *name, uintptr_t arena_start, uintptr_t arena_size) |
| { |
| for (uint i = 0; i < NOVM_MAX_ARENAS; i++) { |
| if (arena[i].pages == 0) { |
| novm_init_helper(&arena[i], name, arena_start, arena_size, NULL, 0); |
| return; |
| } |
| } |
| panic("novm_add_arena: too many arenas added, bump NOVM_MAX_ARENAS!\n"); |
| } |
| |
| static void novm_init(uint level) |
| { |
| static char mem_allocation_map[DEFAULT_MAP_SIZE]; |
| novm_init_helper(&arena[0], "main", MEM_START, MEM_SIZE, mem_allocation_map, DEFAULT_MAP_SIZE); |
| } |
| |
| LK_INIT_HOOK(novm, &novm_init, LK_INIT_LEVEL_PLATFORM_EARLY - 1); |
| |
| void *novm_alloc_helper(struct novm_arena *n, size_t pages) |
| { |
| if (pages == 0 || pages > n->pages) |
| return NULL; |
| |
| mutex_acquire(&n->lock); |
| for (size_t i = 0; i <= n->pages - pages; i++) { |
| bool found = true; |
| for (size_t j = 0; j < pages; j++) { |
| if (n->map[i + j] != 0) { |
| i += j; |
| found = false; |
| break; |
| } |
| } |
| if (found) { |
| memset(n->map + i, 1, pages); |
| mutex_release(&n->lock); |
| return n->base + (i << PAGE_SIZE_SHIFT); |
| } |
| } |
| mutex_release(&n->lock); |
| |
| return NULL; |
| } |
| |
| void *novm_alloc_pages(size_t pages, uint32_t arena_bitmap) |
| { |
| LTRACEF("pages %zu\n", pages); |
| |
| /* allocate from any arena */ |
| for (uint i = 0; i < NOVM_MAX_ARENAS; i++) { |
| if (arena_bitmap & (1U << i)) { |
| void *result = novm_alloc_helper(&arena[i], pages); |
| if (result) |
| return result; |
| } |
| } |
| |
| return NULL; |
| } |
| |
| void novm_free_pages(void *address, size_t pages) |
| { |
| LTRACEF("address %p, pages %zu\n", address, pages); |
| |
| struct novm_arena *n = NULL; |
| for (uint i = 0; i < NOVM_MAX_ARENAS; i++) { |
| if (in_arena(&arena[i], address)) { |
| n = &arena[i]; |
| break; |
| } |
| } |
| if (!n) |
| return; |
| |
| DEBUG_ASSERT(in_arena(n, address)); |
| |
| size_t index = ((char *)address - (char *)(n->base)) >> PAGE_SIZE_SHIFT; |
| char *map = n->map; |
| |
| mutex_acquire(&n->lock); |
| for (size_t i = 0; i < pages; i++) map[index + i] = 0; |
| mutex_release(&n->lock); |
| } |
| |
| status_t novm_alloc_specific_pages(void *address, size_t pages) |
| { |
| LTRACEF("address %p, pages %zu\n", address, pages); |
| |
| struct novm_arena *n = NULL; |
| for (uint i = 0; i < NOVM_MAX_ARENAS; i++) { |
| if (in_arena(&arena[i], address)) { |
| n = &arena[i]; |
| break; |
| } |
| } |
| if (!n) |
| return ERR_NOT_FOUND; |
| |
| size_t index = ((char *)address - (char *)(n->base)) >> PAGE_SIZE_SHIFT; |
| char *map = n->map; |
| |
| status_t err = NO_ERROR; |
| |
| mutex_acquire(&n->lock); |
| for (size_t i = 0; i < pages; i++) { |
| if (map[index + i] != 0) { |
| err = ERR_NO_MEMORY; |
| break; |
| } |
| map[index + i] = 1; |
| } |
| mutex_release(&n->lock); |
| |
| return err; |
| } |
| |
| |
| #if LK_DEBUGLEVEL > 1 |
| #if WITH_LIB_CONSOLE |
| |
| #include <lib/console.h> |
| |
| static int cmd_novm(int argc, const cmd_args *argv); |
| static void novm_dump(void); |
| |
| STATIC_COMMAND_START |
| STATIC_COMMAND("novm", "page allocator (for devices without VM support) debug commands", &cmd_novm) |
| STATIC_COMMAND_END(novm); |
| |
| static int cmd_novm(int argc, const cmd_args *argv) |
| { |
| if (argc < 2) { |
| notenoughargs: |
| printf("not enough arguments\n"); |
| usage: |
| printf("usage:\n"); |
| printf("\t%s info\n", argv[0].str); |
| printf("\t%s alloc <numberofpages> [arena bitmap]\n", argv[0].str); |
| printf("\t%s free <address> [numberofpages]\n", argv[0].str); |
| return -1; |
| } |
| |
| if (strcmp(argv[1].str, "info") == 0) { |
| novm_dump(); |
| } else if (strcmp(argv[1].str, "alloc") == 0) { |
| if (argc < 3) goto notenoughargs; |
| |
| uint32_t arena_bitmap = (argc >= 4) ? argv[3].u : NOVM_ARENA_ANY; |
| void *ptr = novm_alloc_pages(argv[2].u, arena_bitmap); |
| printf("novm_alloc_pages returns %p\n", ptr); |
| } else if (strcmp(argv[1].str, "free") == 0) { |
| if (argc < 3) goto notenoughargs; |
| size_t pages = (argc >= 4) ? argv[3].u : 1; |
| novm_free_pages(argv[2].p, pages); |
| printf("novm_free_pages: %zd pages at %p\n", pages, argv[2].p); |
| } else { |
| printf("unrecognized command\n"); |
| goto usage; |
| } |
| |
| return 0; |
| } |
| |
| static void novm_dump_arena(struct novm_arena *n) |
| { |
| if (n->pages == 0) { |
| return; |
| } |
| |
| mutex_acquire(&n->lock); |
| printf("name '%s', %d pages, each %zdk (%zdk in all)\n", n->name, n->pages, PAGE_SIZE >> 10, (PAGE_SIZE * n->pages) >> 10); |
| printf(" range: %p-%p\n", (void *)n->base, (char *)n->base + n->size); |
| printf(" unaligned range: %p-%p\n", n->unaligned_area, n->unaligned_area + n->unaligned_size); |
| unsigned i; |
| size_t in_use = 0; |
| for (i = 0; i < n->pages; i++) if (n->map[i] != 0) in_use++; |
| printf(" %zd/%zd in use\n", in_use, n->pages); |
| #define MAX_PRINT 1024u |
| for (i = 0; i < MAX_PRINT && i < n->pages; i++) { |
| if ((i & 63) == 0) printf(" "); |
| printf("%c", n->map[i] ? '*' : '.'); |
| if ((i & 63) == 63) printf("\n"); |
| } |
| if (i == MAX_PRINT && n->pages > MAX_PRINT) { |
| printf(" etc., %zd more pages.", n->pages - MAX_PRINT); |
| } |
| printf("\n"); |
| mutex_release(&n->lock); |
| } |
| |
| static void novm_dump(void) |
| { |
| for (uint i = 0; i < NOVM_MAX_ARENAS; i++) { |
| novm_dump_arena(&arena[i]); |
| } |
| } |
| |
| #endif |
| #endif |
| |