diff options
| author | Kimplul <kimi.h.kuparinen@gmail.com> | 2024-07-05 19:38:11 +0300 |
|---|---|---|
| committer | Kimplul <kimi.h.kuparinen@gmail.com> | 2024-07-05 19:38:11 +0300 |
| commit | e09edb5d54e39bd9509a1dc450df5ab320759f97 (patch) | |
| tree | 78e050b0db87df3a3ddfbb6570d44513ac34e02a /src | |
| parent | 98f21e694a6b0964d6d08196cb6bfbc1c2ae2ff4 (diff) | |
| download | kmi-e09edb5d54e39bd9509a1dc450df5ab320759f97.tar.gz kmi-e09edb5d54e39bd9509a1dc450df5ab320759f97.zip | |
allow booting with Qemu's -kernel flag directly
+ Took some fairly significant changes, for one the kernel is no longer
relocated at the start of a boot, instead it sits wherever the user
decides the kernel should sit. Similarly, the initial kernel stack and
page table are stored within the binary, slightly bloating the size
but making it much safer to boot since there's really no chance of us
overwriting the fdt or initrd in memory.
Diffstat (limited to 'src')
| -rw-r--r-- | src/initrd.c | 40 | ||||
| -rw-r--r-- | src/main.c | 92 | ||||
| -rw-r--r-- | src/mem.c | 22 | ||||
| -rw-r--r-- | src/pmem.c | 178 | ||||
| -rw-r--r-- | src/proc.c | 29 | ||||
| -rw-r--r-- | src/tcb.c | 8 | ||||
| -rw-r--r-- | src/vmem.c | 35 |
7 files changed, 255 insertions, 149 deletions
diff --git a/src/initrd.c b/src/initrd.c index caff8b3..242e373 100644 --- a/src/initrd.c +++ b/src/initrd.c @@ -115,53 +115,39 @@ static size_t init_nlen = ARRAY_SIZE(init_n) - 1; /* ignore trailing NULL */ pm_t get_initrdtop(const void *fdt) { int chosen_offset = fdt_path_offset(fdt, "/chosen"); - struct cell_info ci = get_cellinfo(fdt, chosen_offset); + int len = 0; void *initrd_end_ptr = (void *)fdt_getprop(fdt, chosen_offset, - "linux,initrd-end", NULL); + "linux,initrd-end", &len); - /* fdt is only aware of physical memory pointers */ - return (pm_t)__va(fdt_load_int_ptr(ci.addr_cells, initrd_end_ptr)); + catastrophic_assert(initrd_end_ptr); + return (pm_t)fdt_load_int_ptr(len / 4, initrd_end_ptr); } pm_t get_initrdbase(const void *fdt) { const int chosen_offset = fdt_path_offset(fdt, "/chosen"); - const struct cell_info ci = get_cellinfo(fdt, chosen_offset); + int len = 0; void *initrd_base_ptr = (void *)fdt_getprop(fdt, chosen_offset, - "linux,initrd-start", NULL); + "linux,initrd-start", &len); - return (pm_t)__va(fdt_load_int_ptr(ci.addr_cells, initrd_base_ptr)); + catastrophic_assert(initrd_base_ptr); + return (pm_t)fdt_load_int_ptr(len / 4, initrd_base_ptr); } - -size_t get_init_size(const void *fdt) +size_t get_initrdsize(const void *fdt) { - char *c = (char *)get_initrdbase(fdt); - struct cpio_header *cp = __find_file(c, init_n, init_nlen); - return convnum(cp->c_filesize, 8, 16); + pm_t start = get_initrdbase(fdt); + pm_t end = get_initrdtop(fdt); + return end - start; } vm_t get_init_base(const void *fdt) { char *c = (char *)get_initrdbase(fdt); + c = __va(c); struct cpio_header *cp = __find_file(c, init_n, init_nlen); size_t name_len = convnum(cp->c_namesize, 8, 16); return ((vm_t)cp) + align_up(sizeof(struct cpio_header) + name_len, 4); } - -stat_t move_init(const void *fdt, void *target) -{ - const char *c = (const char *)get_initrdbase(fdt); - - const struct cpio_header *cp = __find_file(c, init_n, init_nlen); - size_t name_len = convnum(cp->c_namesize, 8, 16); - size_t file_len = convnum(cp->c_filesize, 8, 16); - - char *fp = (char *)cp; - fp += align_up(sizeof(struct cpio_header) + name_len, 4); - - memmove(target, fp, file_len); - return OK; -} @@ -19,6 +19,54 @@ #include <arch/smp.h> #include <libfdt.h> +static pm_t __fdt_ram_base(void *fdt) +{ + int mem_offset = fdt_path_offset(fdt, "/memory"); + const void *mem_reg = fdt_getprop(fdt, mem_offset, "reg", NULL); + + struct cell_info ci = get_cellinfo(fdt, mem_offset); + return (pm_t)fdt_load_reg_addr(ci, mem_reg, 0); +} + +static pm_t __fdt_ram_size(void *fdt) +{ + int mem_offset = fdt_path_offset(fdt, "/memory"); + const void *mem_reg = fdt_getprop(fdt, mem_offset, "reg", NULL); + + /* here we actually want the root offset because /memory itself doesn't + * have children, I guess? */ + struct cell_info ci = get_cellinfo(fdt, fdt_path_offset(fdt, "/")); + return (pm_t)fdt_load_reg_size(ci, mem_reg, 0); +} + +void kernel(void *fdt, uintptr_t load_addr, struct vmem *d) +{ + fdt = __va(fdt); + + /* dbg uses direct mapping at this point */ + init_dbg(fdt); + /* start up debugging in kernel IO */ + setup_io_dbg(d); + + dbg_fdt(fdt); + + setup_arch(fdt); + + init_pmem(fdt, load_addr); + + init_irq(fdt); + init_timer(fdt); + + vm_t proc_fdt = 0, proc_initrd = 0; + init_proc(fdt, &proc_fdt, &proc_initrd); + + /* try to bring up other cores on system */ + smp_bringup(d, fdt); + + /* start running init program */ + run_init(cur_tcb(), proc_fdt, proc_initrd); +} + /** * Boot entry of kernel actual. * @@ -29,35 +77,33 @@ * @param ram_base RAM base. * @return Should not. */ -void __main main(void *fdt, uintptr_t ram_base) +void main(unsigned long hart, void *fdt, uintptr_t load_addr) { - set_ram_base(ram_base); + /* we have our own ways to get the current hart when we need it, but we + * have to get the function signature right */ + (void)hart; - /* convert physical address to virtual address */ - fdt = __va(fdt); - - /* dbg uses direct mapping at this point */ - init_dbg(fdt); - setup_dmap_dbg(); - dbg_fdt(fdt); + pm_t ram_base = __fdt_ram_base(fdt); + pm_t ram_size = __fdt_ram_size(fdt); + set_ram_base(ram_base); + set_ram_size(ram_size); - setup_arch(fdt); + init_mem(fdt); - init_pmem(fdt); - /* setup temporary virtual memory */ - struct vmem *b = init_vmem(fdt); + struct vmem *d = direct_mapping(); - /* start up debugging in kernel IO */ - setup_io_dbg(b); + to_kernelspace(fdt, load_addr, d, ram_base, VM_DMAP); + unreachable(); +} - init_irq(fdt); - init_timer(fdt); - init_proc(fdt); +#if GENERIC_UBOOT +void main_go(size_t argc, char *argv[], uintptr_t load_addr) +{ + if (argc != 2) + return; - /* try to bring up other cores on system */ - smp_bringup(b, fdt); + void *fdt = (void *)strtouintptr(argv[1]); - /* start running init program */ - void *initrd = (void *)get_initrdbase(fdt); - run_init(cur_tcb(), fdt, initrd); + main(0, fdt, load_addr); } +#endif @@ -22,6 +22,7 @@ enum mm_order __mm_max_order; * like __mm_*. */ pm_t ram_base; +size_t ram_size; enum mm_order nearest_order(size_t size) { @@ -32,12 +33,17 @@ enum mm_order nearest_order(size_t size) return MM_O0; } -void init_mem(size_t max_order, size_t bits[10], size_t page_shift) +void init_mem(void *fdt) { + size_t max_order = 0; + size_t base_bits = 0; + size_t bits[NUM_ORDERS] = { 0 }; + stat_pmem_conf(fdt, &max_order, &base_bits, bits); + __mm_max_order = max_order; - __mm_page_shift = page_shift; + __mm_page_shift = base_bits; - __mm_shifts[0] = page_shift; + __mm_shifts[0] = __mm_page_shift; __mm_widths[0] = 1 << bits[0]; __mm_sizes[0] = 1 << __mm_page_shift; @@ -53,7 +59,17 @@ void set_ram_base(uintptr_t base) ram_base = base; } +void set_ram_size(size_t size) +{ + ram_size = size; +} + uintptr_t get_ram_base() { return ram_base; } + +size_t get_ram_size() +{ + return ram_size; +} @@ -436,77 +436,89 @@ static void __mark_area_used(pm_t base, pm_t top) mark_used(BASE_PAGE, runner); } +struct avoid_region { + pm_t base; + pm_t size; +}; + +static bool overlaps(pm_t base1, pm_t size1, pm_t base2, pm_t size2) +{ + bool b = base1 >= base2 && base1 < base2 + size2; + bool t = base1 + size1 > base2 && base1 + size1 <= base2 + size2; + return b || t; +} + /** * Mark reserved memory region used, to avoid it getting accidentally allocated. * * @param fdt Global FDT pointer. */ -static void __mark_reserved_mem(void *fdt) +static void __mark_reserved(pm_t ram_base, pm_t ram_size, size_t avoid_count, + struct avoid_region avoid[64]) +{ + for (size_t i = 0; i < avoid_count; ++i) { + pm_t base = avoid[i].base; + pm_t size = avoid[i].size; + + if (!overlaps(base, size, ram_base, ram_size)) + continue; + + pm_t top = base + size; + __mark_area_used(base, top); + info("marked [%lx - %lx] reserved\n", base, top); + } +} + +static size_t build_reserved_map(size_t exists, struct avoid_region avoid[64], + void *fdt) { int rmem_offset = fdt_path_offset(fdt, "/reserved-memory"); struct cell_info ci = get_cellinfo(fdt, rmem_offset); int node = 0; fdt_for_each_subnode(node, fdt, rmem_offset) { - uint8_t *rmem_reg = - (uint8_t *)fdt_getprop(fdt, node, "reg", NULL); + uint8_t *rmem_reg = (uint8_t *)fdt_getprop(fdt, node, "reg", + NULL); pm_t base = (pm_t)fdt_load_reg_addr(ci, rmem_reg, 0); + pm_t size = (pm_t)fdt_load_reg_size(ci, rmem_reg, 0); - /** @todo make sure the top of a reserved memory area doesn't go - * against our assumptions in FW_MAX_SIZE? */ - pm_t top = (pm_t)fdt_load_reg_size(ci, rmem_reg, 0) + base; - __mark_area_used((pm_t)__va(base), (pm_t)__va(top)); - info("marked [%lx - %lx] reserved\n", - (pm_t)__va(base), (pm_t)__va(top)); + avoid[exists++] = (struct avoid_region){(pm_t)__va(base), size}; + catastrophic_assert(exists < 64); } + + return exists; } -/** - * Read top of RAM from FDT. - * - * @param fdt Global FDT pointer. - * @return Physical address of top of RAM. - */ -static pm_t __get_ramtop(void *fdt) +static pm_t select_base(pm_t ram_base, pm_t ram_size, pm_t size, + pm_t avoid_count, + struct avoid_region avoid[64]) { - int mem_offset = fdt_path_offset(fdt, "/memory"); - const void *mem_reg = fdt_getprop(fdt, mem_offset, "reg", NULL); + for (size_t i = 0; i < avoid_count; ++i) { + /* try placing things just after each avoidance region */ + pm_t base = avoid[i].base + avoid[i].size; - /* here we actually want the root offset because /memory itself doesn't - * have children, I guess? */ - struct cell_info ci = get_cellinfo(fdt, fdt_path_offset(fdt, "/")); - pm_t base = (pm_t)fdt_load_reg_addr(ci, mem_reg, 0); - return (pm_t)fdt_load_reg_size(ci, mem_reg, 0) + base; -} + /* any better alignment than this has to be manually handled + * outside of this function */ + base = align_up(base, sizeof(long)); -/** - * Read top of FDT. - * - * @param fdt Global FDT pointer. - * @return Physical address of top of FDT. - */ -static pm_t __get_fdttop(void *fdt) -{ - const char *b = (const char *)fdt; - return (pm_t)(b + fdt_totalsize(fdt)); -} + if (!overlaps(base, size, ram_base, ram_size)) + continue; -/** - * Return base of FDT. - * - * Technically pretty useless, but here mainly for cohesion. - * - * @param fdt Global FDT pointer. - * @return \c fdt. - */ -static pm_t __get_fdtbase(void *fdt) -{ - /* lol */ - return (pm_t)fdt; + for (size_t a = 0; a < avoid_count; ++a) { + if (overlaps(base, size, avoid[a].base, avoid[a].size)) + goto retry; + + } + + return base; +retry: + } + + return 0; } -void init_pmem(void *fdt) +void init_pmem(void *fdt, uintptr_t load_addr) { /** @todo should I keep the info outputs? I suppose it's nice to see * if any assumption is being broken in the serial log, but in that case @@ -515,14 +527,10 @@ void init_pmem(void *fdt) */ info("initializing pmem\n"); - size_t max_order = 0; - size_t base_bits = 0; - size_t bits[NUM_ORDERS] = { 0 }; - stat_pmem_conf(fdt, &max_order, &base_bits, bits); - init_mem(max_order, bits, base_bits); - - pm_t ram_size = __get_ramtop(fdt) - get_ram_base(); + /* here it's a bit easier to work with virtual RAM addresses, but + * physical ones could work just as well. */ pm_t ram_base = (pm_t)__va(get_ram_base()); + pm_t ram_size = get_ram_size(); info("using ram range [%lx - %lx]\n", ram_base, ram_base + ram_size); @@ -530,23 +538,38 @@ void init_pmem(void *fdt) /** @todo could probably improve error messages on failing to get fdt * values */ pm_t initrd_base = get_initrdbase(fdt); - pm_t initrd_top = get_initrdtop(fdt); - info("found initrd at [%lx - %lx]\n", initrd_base, initrd_top); + pm_t initrd_size = get_initrdsize(fdt); + info("found initrd at [%lx - %lx]\n", initrd_base, + initrd_base + initrd_size); - pm_t fdt_top = __get_fdttop(fdt); - pm_t fdt_base = __get_fdtbase(fdt); - info("found fdt at [%lx - %lx]\n", fdt_base, fdt_top); + pm_t fdt_base = (pm_t)fdt; + pm_t fdt_size = fdt_totalsize(fdt); + info("found fdt at [%lx - %lx]\n", fdt_base, fdt_base + fdt_size); /* find probably most suitable contiguous region of ram for our physical * ram map */ - /** @todo this really should check that there's enough space in RAM - * instead of just forcing the pmap to be populated */ - pm_t pmap_base = align_up(MAX(initrd_top, fdt_top), BASE_PAGE_SIZE); - info("choosing to place pmem map at %lx\n", pmap_base); - size_t probe_size = probe_pmap(ram_base, ram_size, pmap_base); + size_t probe_size = probe_pmap(0, ram_size, 0); info("pmem map probe size returned %lu\n", probe_size); + /* linker magicry */ + extern char *__kernel_size; + /* avoidance regions, note that stack and so on is included in the + * kernel. Addresses can be outside RAM, in which case they are just + * ignored. */ + struct avoid_region avoid[64] = { + {(pm_t)__va(load_addr), (pm_t)&__kernel_size}, + {(pm_t)__va(initrd_base), initrd_size}, + {(pm_t)__va(fdt_base), fdt_size} + }; + + size_t avoid_count = build_reserved_map(4, avoid, fdt); + pm_t pmap_base = select_base(ram_base, ram_size, + probe_size, avoid_count, avoid); + + catastrophic_assert(pmap_base); + info("choosing to place pmem map at %lx\n", pmap_base); + size_t actual_size = populate_pmap(ram_base, ram_size, pmap_base); info("pmem map actual size %lu\n", actual_size); @@ -555,31 +578,10 @@ void init_pmem(void *fdt) actual_size); } - /* mark init stack, this should be unmapped once we get to executing - * processes */ - __mark_area_used(VM_STACK_BASE, VM_STACK_TOP); - info("marked stack [%lx - %lx] used\n", VM_STACK_BASE, VM_STACK_TOP); - - /* mark kernel */ - /* this could be made more explicit, I suppose. */ - __mark_area_used(VM_KERN, VM_KERN + PM_KERN_SIZE); - info("marked kernel [%lx - %lx] used\n", VM_KERN, - VM_KERN + PM_KERN_SIZE); - - /* mark fdt and initrd */ - __mark_area_used(initrd_base, initrd_top); - info("marked initrd [%lx - %lx] used\n", initrd_base, initrd_top); - - __mark_area_used(fdt_base, fdt_top); - info("marked fdt [%lx - %lx] used\n", fdt_base, fdt_top); - - /* mark pmap */ - __mark_area_used(pmap_base, pmap_base + actual_size); - info("marked pmap [%lx - %lx] used\n", pmap_base, - pmap_base + actual_size); + avoid[avoid_count++] = (struct avoid_region){pmap_base, actual_size}; /* mark reserved mem */ - __mark_reserved_mem(fdt); + __mark_reserved(ram_base, ram_size, avoid_count, avoid); init_mem_nodes(); @@ -9,12 +9,15 @@ #include <kmi/elf.h> #include <kmi/proc.h> #include <kmi/conf.h> +#include <kmi/debug.h> #include <kmi/string.h> #include <kmi/initrd.h> #include <arch/arch.h> #include <arch/proc.h> #include <arch/cpu.h> +#include <libfdt.h> + stat_t prepare_proc(struct tcb *t, vm_t bin, vm_t interp) { vm_t entry = load_elf(t, bin, interp); @@ -27,7 +30,7 @@ stat_t prepare_proc(struct tcb *t, vm_t bin, vm_t interp) return OK; } -stat_t init_proc(void *fdt) +stat_t init_proc(void *fdt, vm_t *proc_fdt, vm_t *proc_initrd) { init_tcbs(); @@ -42,18 +45,32 @@ stat_t init_proc(void *fdt) /* force tcb for core */ tcb_assign(t); - /* set current tcb */ - use_tcb(t); + t->notify_id = t->tid; /* init process has all capabilities */ set_caps(t->caps, 0, CAP_CAPS | CAP_PROC | CAP_SIGNAL | CAP_POWER | CAP_NOTIFY); - t->notify_id = t->tid; + /* we shall try to map the fdt and initrd into the new address space, so + * save them here before we switch */ + use_tcb(t); - /* allocate stacks after ELF file to make sure nothing of importance + /* allocate stacks etc after ELF file to make sure nothing of importance * clashes */ - return prepare_proc(t, get_init_base(fdt), 0); + prepare_proc(t, get_init_base(fdt), 0); /** \todo start one thread per core, with special handling for init in * that each thread starts at the entry point of init? */ + + *proc_fdt = map_fixed_mem(t, + (pm_t)fdt, fdt_totalsize(fdt), + VM_V | VM_R | VM_U); + + pm_t initrd = (pm_t)__va(get_initrdbase(fdt)); + *proc_initrd = map_fixed_mem(t, + initrd, get_initrdsize(fdt), + VM_V | VM_R | VM_U); + + info("mapped fdt at %p\n", (void *)*proc_fdt); + info("mapped initrd at %p\n", (void *)*proc_initrd); + return OK; } @@ -74,7 +74,7 @@ static id_t __alloc_tid(struct tcb *t) if (i == stop_tid) return ERR_NF; - if (get_tcb(i) || i == 0) + if (tcbs[i & (num_tids - 1)] || i == 0) continue; tcbs[i & (num_tids - 1)] = t; @@ -210,6 +210,11 @@ static stat_t __destroy_thread_data(struct tcb *t) destroy_vmem(t->rpc.vmem); /* remove ourselves from the thread pool */ + /** @todo this should be at the top of the function, and be wrapped in + * some kind of lock that checks that nobody reads the value while we're + * setting it to zero. get_tcb() should accordingly increment the + * reference count atomically. Also, an unget_tcb() is needed to + * decrement the reference count I guess? */ tcbs[t->tid] = 0; /* forcefully free last struggling bits of memory */ @@ -228,6 +233,7 @@ stat_t destroy_thread(struct tcb *t) hard_assert(!is_proc(t), ERR_INVAL); /* mark us as zombies */ + set_bits(t->state, TCB_ZOMBIE); t->rid = 0; /* remove reference to root process */ @@ -232,6 +232,34 @@ vm_t alloc_fixed_uvmem(struct tcb *t, vm_t start, size_t size, vmflags_t flags) return w; } +static vm_t map_fixed_region(struct vmem *b, vm_t v, pm_t p, size_t size, + vmflags_t flags, stat_t *status) +{ + + vm_t w = v; + stat_t stat = OK; + size_t pages = size / BASE_PAGE_SIZE; + for (size_t i = 0; i < pages; ++i) { + stat = map_vpage(b, p, v, flags, BASE_PAGE); + v += BASE_PAGE_SIZE; + p += BASE_PAGE_SIZE; + } + + if (status) + *status = stat; + + return w; +} + +vm_t map_fixed_mem(struct tcb *t, pm_t start, size_t size, vmflags_t flags) +{ + stat_t status = OK; + const vm_t v = alloc_region(&t->sp_r, size, &size, flags); + const vm_t w = map_fixed_region(t->proc.vmem, v, start, size, flags, + &status); + return w + (start % BASE_PAGE_SIZE); +} + /* free_shared_uvmem shouldn't be needed, likely to work with free_uvmem */ stat_t alloc_shared_uvmem(struct tcb *s, struct tcb *c, size_t size, vmflags_t sflags, vmflags_t cflags, @@ -341,9 +369,14 @@ stat_t copy_allocd_wrapper(struct vmem *b, pm_t *offset, vm_t vaddr, if (!new_page) return INFO_TRGN; - map_vpage(b, new_page, vaddr, flags, order); + /* set write flags temporarily */ + vmflags_t wrflags = flags | VM_W; + map_vpage(b, new_page, vaddr, wrflags, order); memcpy((void *)new_page, (void *)(paddr + *offset), order_size(order)); + /* set actual flags */ + map_vpage(b, new_page, vaddr, flags, order); + if (v_order > order) *offset += order_size(order); else |
