/* SPDX-License-Identifier: copyleft-next-0.3.1 */ /* Copyright 2021 - 2022, Kim Kuparinen < kimi.h.kuparinen@gmail.com > */ /** * @file vmem.c * Virtual memory handling, mainly userspace virtual memory. */ #include #include #include #include #include #include #include stat_t init_uvmem(struct tcb *t) { t->uvmem.owner = t->tid; t->uvmem.vmem = t->proc.vmem; /* reserve 64KiB (arbitrary number but should be large enough that * nobody accidentally indexes above NULL enough to find real memory) */ return init_region(&t->uvmem.region, UVMEM_START, UVMEM_END - UVMEM_START, SZ_64K); } /** * Copy process memory region. * * @param d Destination tcb. * @param s Source tcb. * @param m Memory region to clone. * @return \ref ERR_MISC if clone failed, otherwise \ref OK. * * @todo check shared memory regions. */ static stat_t __copy_mapped_region(struct tcb *d, struct tcb *s, struct mem_region *m) { vm_t start = m->start * BASE_PAGE_SIZE; vm_t end = m->end * BASE_PAGE_SIZE; size_t size = end - start; vm_t v = alloc_fixed_region(&d->uvmem.region, start, size, &size, m->flags); if (ERR_CODE(v)) return v; if (is_set(m->flags, MR_NONBACKED)) return v; assert(v == start); /* note that we use uvmem.vmem instead of proc.vmem, this is just to * make sure that zombies don't eat our brains */ stat_t res = copy_region(d->uvmem.vmem, s->uvmem.vmem, v, v, size); if (res == OK) return OK; /* cleanup on error */ free_region(&d->uvmem.region, v); unmap_region(d->uvmem.vmem, v, size); return res; } /** * Copy shared regions to new process. In these cases, we want to both allocate * a fixed region and map some fixed physical memory. * * @param d 'Destination' * @param m Shared memory region. * @return \ref OK on success, some error code otherwise. */ static stat_t __copy_shared_region(struct tcb *d, struct mem_region *m) { struct tcb *s = get_tcb(m->pid); if (!s) return ERR_NF; vm_t start = m->start * BASE_PAGE_SIZE; vm_t end = m->end * BASE_PAGE_SIZE; reference_proc(s); size_t size = end - start; vm_t v = alloc_fixed_region(&d->uvmem.region, start, size, &size, m->flags); if (ERR_CODE(v)) return v; assert(v == start); stat_t res = clone_region(d->uvmem.vmem, s->uvmem.vmem, start, v, size, m->flags); if (res == OK) return OK; /* cleanup on error */ free_region(&d->uvmem.region, v); unmap_fixed_region(d->uvmem.vmem, v, size); return res; } /** * Helper for implementing shared memory. * * @param d 'Destination' of new mapping. * @param s Owner of shared region. * @param m Shared memory region to clone. * @param flags Flags of new mapping. * @return Address of new mapping in \p d. */ static vm_t __clone_shared_region(struct tcb *d, struct tcb *s, struct mem_region *m, vmflags_t flags) { vm_t start = m->start * BASE_PAGE_SIZE; vm_t end = m->end * BASE_PAGE_SIZE; reference_proc(s); size_t size = end - start; vm_t v = alloc_shared_region(&d->uvmem.region, size, &size, MR_NONBACKED | m->flags, s->rid); if (ERR_CODE(v)) return v; stat_t res = clone_region(d->uvmem.vmem, s->uvmem.vmem, start, v, size, flags); if (res == OK) return v; /* cleanup on error */ unreference_proc(s); free_region(&d->uvmem.region, v); unmap_fixed_region(d->uvmem.vmem, v, size); return res; } /** * Unmap and free private memory region. * * @param t Current thread. * @param m Memory region to free. */ static void __free_mapping(struct tcb *t, struct mem_region *m) { struct tcb *owner = get_tcb(m->pid); if (owner) unreference_proc(owner); if (is_set(m->flags, MR_NONBACKED)) return; pm_t start = __addr(m->start); pm_t end = __addr(m->end); size_t size = end - start; if (m->pid) unmap_fixed_region(t->proc.vmem, start, size); else unmap_region(t->proc.vmem, start, size); } void clear_uvmem(struct tcb *t) { if (t->uvmem.owner != t->tid) return; struct mem_region *m = find_closest_used_region(&t->uvmem.region, 0); for (; m; m = m->next) { if (is_region_kept(m)) continue; if (!is_set(m->flags, MR_USED)) continue; __free_mapping(t, m); free_known_region(&t->uvmem.region, m); } } void purge_uvmem(struct tcb *t) { if (t->uvmem.owner != t->tid) return; struct mem_region *m = find_closest_used_region(&t->uvmem.region, 0); for (; m; m = m->next) { if (!is_set(m->flags, MR_USED)) continue; __free_mapping(t, m); } /* actually destroy region, will clear out all nodes automatically */ destroy_region(&t->uvmem.region); } void destroy_uvmem(struct tcb *t) { if (t->uvmem.owner != t->tid) return; /* force clear all regions */ purge_uvmem(t); /* destroy associated virtual memory space */ destroy_vmem(t->uvmem.vmem); } stat_t copy_uvmem(struct tcb *d, struct tcb *s) { /** @todo implement some way to only iterate used regions, this loops * through all regions which is likely a slight bit slower. */ stat_t ret = OK; struct mem_region *m = find_first_region(&s->uvmem.region); for (; m; m = m->next) { if (!is_region_used(m)) continue; if (m->pid == 0) ret = __copy_mapped_region(d, s, m); else ret = __copy_shared_region(d, m); if (ret) return ret; } return ret; } vm_t alloc_uvmem(struct tcb *t, size_t size, vmflags_t flags) { /* t exists and is the process tcb of the current process */ assert(t && is_proc(t)); const vm_t v = alloc_region(&t->uvmem.region, size, &size, flags); if (ERR_CODE(v)) return v; stat_t ret = OK; if ((ret = map_region(t->proc.vmem, v, size, max_order(), flags))) { unmap_region(t->proc.vmem, v, size); free_region(&t->uvmem.region, v); return ret; } return v; } vm_t alloc_fixed_uvmem(struct tcb *t, vm_t start, size_t size, vmflags_t flags) { assert(t && is_proc(t)); const vm_t v = alloc_fixed_region(&t->uvmem.region, start, size, &size, flags); if (ERR_CODE(v)) return v; stat_t ret = OK; if ((ret = map_region(t->proc.vmem, v, size, max_order(), flags))) { unmap_region(t->proc.vmem, v, size); free_region(&t->uvmem.region, v); return ret; } return v; } vm_t map_fixed_uvmem(struct tcb *t, pm_t start, size_t size, vmflags_t flags) { assert(is_aligned(start, BASE_PAGE_SIZE)); const vm_t v = alloc_region(&t->uvmem.region, size, &size, flags); if (ERR_CODE(v)) return v; stat_t ret = OK; if ((ret = map_fixed_region(t->proc.vmem, v, start, size, flags))) { unmap_region(t->proc.vmem, v, size); free_region(&t->uvmem.region, v); return ret; } return v; } vm_t alloc_uvpage(struct tcb *t, size_t size, vmflags_t flags, pm_t *startp, size_t *sizep) { enum mm_order order = nearest_order(size); size = order_size(order); const vm_t v = alloc_region(&t->uvmem.region, size, &size, flags); if (ERR_CODE(v)) return v; pm_t start = alloc_page(order); if (!start) { free_region(&t->uvmem.region, size); return ERR_OOMEM; } stat_t ret = OK; if ((ret = map_fixed_region(t->proc.vmem, v, start, size, flags))) { unmap_region(t->proc.vmem, v, size); free_region(&t->uvmem.region, v); return ret; } *startp = (pm_t)__pa(start); *sizep = size; return v; } /* free_shared_uvmem shouldn't be needed, likely to work with free_uvmem */ vm_t alloc_shared_uvmem(struct tcb *s, size_t size, vmflags_t flags) { assert(s && is_proc(s)); const vm_t v = alloc_region(&s->uvmem.region, size, &size, MR_SHARED | flags); if (ERR_CODE(v)) return v; /* use base pages to make clone more likely to succeed */ stat_t ret = OK; if ((ret = map_region(s->proc.vmem, v, size, BASE_PAGE, flags))) { unmap_region(s->proc.vmem, v, size); free_region(&s->uvmem.region, v); return ret; } return v; } vm_t ref_shared_uvmem(struct tcb *d, struct tcb *s, vm_t v, vmflags_t flags) { struct mem_region *m = find_used_region(&s->uvmem.region, v); if (!m) return ERR_NF; if (!is_set(m->flags, MR_SHARED)) return ERR_INVAL; return __clone_shared_region(d, s, m, flags); } stat_t free_uvmem(struct tcb *r, vm_t va) { /** \todo assume tcb is root tcb? */ struct mem_region *m = find_used_region(&r->uvmem.region, va); if (!m) return ERR_NF; __free_mapping(r, m); free_known_region(&r->uvmem.region, m); return OK; } vmflags_t sanitize_uvflags(vmflags_t flags) { return (flags & (VM_R | VM_W | VM_X)) | VM_V | VM_U; }