| /* Copyright (c) 2017 Google Inc. |
| * See LICENSE for details. |
| * |
| * Memory, paging, e820, bootparams and other helpers */ |
| |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <sys/mman.h> |
| #include <ros/arch/mmu.h> |
| #include <vmm/linux_bootparam.h> |
| #include <vmm/vmm.h> |
| #include <err.h> |
| |
| #define ALIGNED(p, a) (!(((uintptr_t)(p)) & ((a)-1))) |
| |
| static char *entrynames[] = { |
| [E820_RAM] "E820_RAM", |
| [E820_RESERVED] "E820_RESERVED", |
| [E820_ACPI] "E820_ACPI", |
| [E820_NVS] "E820_NVS", |
| [E820_UNUSABLE] "E820_UNUSABLE", |
| }; |
| |
| static void dumpe820(struct e820entry *e, int nr) |
| { |
| for (int i = 0; i < nr; i++) { |
| fprintf(stderr, "%d:%p %p %p %s\n", |
| i, e[i].addr, e[i].size, e[i].type, |
| entrynames[e[i].type]); |
| } |
| } |
| |
| // e820map creates an e820 map in the bootparams struct. If we've |
| // gotten here, then memsize and memstart are valid. It returns |
| // pointer to the first page after the map for our bump allocator. We |
| // assume the ranges passed in are validated already. |
| void *init_e820map(struct boot_params *bp, |
| unsigned long long memstart, |
| unsigned long long memsize) |
| { |
| unsigned long long lowmem = 0; |
| // Everything in Linux at this level is PGSIZE. |
| memset(bp, 0, PGSIZE); |
| |
| bp->e820_entries = 0; |
| |
| // The first page is always reserved. |
| bp->e820_map[bp->e820_entries].addr = 0; |
| bp->e820_map[bp->e820_entries].size = PGSIZE; |
| bp->e820_map[bp->e820_entries++].type = E820_RESERVED; |
| |
| /* Give it just a tiny bit of memory -- 60k -- at low memory. */ |
| bp->e820_map[bp->e820_entries].addr = PGSIZE; |
| bp->e820_map[bp->e820_entries].size = LOW64K - PGSIZE; |
| bp->e820_map[bp->e820_entries++].type = E820_RAM; |
| |
| // All other memory from 64k to memstart is reserved. |
| bp->e820_map[bp->e820_entries].addr = LOW64K; |
| bp->e820_map[bp->e820_entries].size = memstart - LOW64K; |
| bp->e820_map[bp->e820_entries++].type = E820_RESERVED; |
| |
| // If memory starts below RESERVED, then add an entry for memstart to |
| // the smaller of RESERVED or memsize. |
| if (memstart < RESERVED) { |
| bp->e820_map[bp->e820_entries].addr = memstart; |
| if (memstart + memsize > RESERVED) |
| bp->e820_map[bp->e820_entries].size = RESERVED - memstart; |
| else |
| bp->e820_map[bp->e820_entries].size = memsize; |
| lowmem = bp->e820_map[bp->e820_entries].size; |
| bp->e820_map[bp->e820_entries++].type = E820_RAM; |
| } |
| |
| bp->e820_map[bp->e820_entries].addr = RESERVED; |
| bp->e820_map[bp->e820_entries].size = RESERVEDSIZE; |
| bp->e820_map[bp->e820_entries++].type = E820_RESERVED; |
| |
| if ((memstart + memsize) > RESERVED) { |
| bp->e820_map[bp->e820_entries].addr = MAX(memstart, _4GiB); |
| bp->e820_map[bp->e820_entries].size = memsize - lowmem; |
| bp->e820_map[bp->e820_entries++].type = E820_RAM; |
| } |
| |
| dumpe820(bp->e820_map, bp->e820_entries); |
| return (void *)bp + PGSIZE; |
| } |
| |
| /* checkmemaligned verifies alignment attributes of your memory space. |
| * It terminates your process with extreme prejudice if they are |
| * incorrect in some way. */ |
| void checkmemaligned(uintptr_t memstart, size_t memsize) |
| { |
| if (!ALIGNED(memstart, PML1_REACH)) |
| errx(1, "memstart (%#x) wrong: must be aligned to %#x", |
| memstart, PML1_REACH); |
| if (!ALIGNED(memsize, PML1_REACH)) |
| errx(1, "memsize (%#x) wrong: must be aligned to %#x", |
| memsize, PML1_REACH); |
| } |
| |
| // memory allocates memory for the VM. It's a complicated mess because of the |
| // break for APIC and other things. We just go ahead and leave the region from |
| // RESERVED to _4GiB for that. The memory is either split, all low, or all |
| // high. This code is designed for a kernel. Dune-style code does not need it |
| // as it does not have the RESERVED restrictions. Dune-style code can use this, |
| // however, by setting memstart to 4 GiB. This code can be called multiple |
| // times with more ranges. It does not check for overlaps. |
| void mmap_memory(struct virtual_machine *vm, uintptr_t memstart, size_t memsize) |
| { |
| void *r1, *r2; |
| unsigned long r1size = memsize; |
| |
| // Let's do some minimal validation, so we don't drive |
| // people crazy. |
| checkmemaligned(memstart, memsize); |
| if ((memstart >= RESERVED) && (memstart < _4GiB)) |
| errx(1, "memstart (%#x) wrong: must be < %#x or >= %#x\n", |
| memstart, RESERVED, _4GiB); |
| if (memstart < MinMemory) |
| errx(1, "memstart (%#x) wrong: must be > %#x\n", |
| memstart, MinMemory); |
| |
| // Note: this test covers the split case as well as the |
| // 'all above 4G' case. |
| if ((memstart + memsize) > RESERVED) { |
| unsigned long long r2start = MAX(memstart, _4GiB); |
| |
| r1size = memstart < RESERVED ? RESERVED - memstart : 0; |
| r2 = mmap((void *)r2start, memsize - r1size, |
| PROT_READ | PROT_WRITE | PROT_EXEC, |
| MAP_POPULATE | MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); |
| if (r2 != (void *)r2start) { |
| fprintf(stderr, |
| "High region: Could not mmap 0x%lx bytes at 0x%lx\n", |
| memsize, r2start); |
| exit(1); |
| } |
| if (memstart >= _4GiB) |
| goto done; |
| } |
| |
| r1 = mmap((void *)memstart, r1size, |
| PROT_READ | PROT_WRITE | PROT_EXEC, |
| MAP_POPULATE | MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); |
| if (r1 != (void *)memstart) { |
| fprintf(stderr, "Low region: Could not mmap 0x%lx bytes at 0x%lx\n", |
| memsize, memstart); |
| exit(1); |
| } |
| |
| done: |
| if ((vm->minphys == 0) || (vm->minphys > memstart)) |
| vm->minphys = memstart; |
| |
| if (vm->maxphys < memstart + memsize - 1) |
| vm->maxphys = memstart + memsize - 1; |
| } |