Catch negative FDs
[akaros.git] / kern / src / mm.c
index 58bbb55..90a53c4 100644 (file)
@@ -38,13 +38,9 @@ void vmr_init(void)
  * tree of some sort for easier lookups. */
 struct vm_region *create_vmr(struct proc *p, uintptr_t va, size_t len)
 {
-       struct vm_region *vmr = 0, *vm_i, *vm_link;
+       struct vm_region *vmr = 0, *vm_i, *vm_next;
        uintptr_t gap_end;
 
-       /* Don't allow a vm region into the 0'th page (null ptr issues) */
-       if (va == 0)
-               va = 1 * PGSIZE;
-
        assert(!PGOFF(va));
        assert(!PGOFF(len));
        assert(va + len <= UMAPTOP);
@@ -59,17 +55,17 @@ struct vm_region *create_vmr(struct proc *p, uintptr_t va, size_t len)
                TAILQ_INSERT_HEAD(&p->vm_regions, vmr, vm_link);
        } else {
                TAILQ_FOREACH(vm_i, &p->vm_regions, vm_link) {
-                       vm_link = TAILQ_NEXT(vm_i, vm_link);
-                       gap_end = vm_link ? vm_link->vm_base : UMAPTOP;
+                       vm_next = TAILQ_NEXT(vm_i, vm_link);
+                       gap_end = vm_next ? vm_next->vm_base : UMAPTOP;
                        /* skip til we get past the 'hint' va */
                        if (va >= gap_end)
                                continue;
-                       /* Found a gap that is big enough */
+                       /* Find a gap that is big enough */
                        if (gap_end - vm_i->vm_end >= len) {
                                vmr = kmem_cache_alloc(vmr_kcache, 0);
                                /* if we can put it at va, let's do that.  o/w, put it so it
                                 * fits */
-                               if (gap_end >= va + len)
+                               if ((gap_end >= va + len) && (va >= vm_i->vm_end))
                                        vmr->vm_base = va;
                                else
                                        vmr->vm_base = vm_i->vm_end;
@@ -105,11 +101,11 @@ struct vm_region *split_vmr(struct vm_region *old_vmr, uintptr_t va)
        new_vmr->vm_base = va;
        new_vmr->vm_end = old_vmr->vm_end;
        old_vmr->vm_end = va;
-       new_vmr->vm_perm = old_vmr->vm_perm;
+       new_vmr->vm_prot = old_vmr->vm_prot;
        new_vmr->vm_flags = old_vmr->vm_flags;
        if (old_vmr->vm_file) {
+               kref_get(&old_vmr->vm_file->f_kref, 1);
                new_vmr->vm_file = old_vmr->vm_file;
-               atomic_inc(&new_vmr->vm_file->f_refcnt);
                new_vmr->vm_foff = old_vmr->vm_foff +
                                      old_vmr->vm_end - old_vmr->vm_base;
        } else {
@@ -125,7 +121,7 @@ int merge_vmr(struct vm_region *first, struct vm_region *second)
 {
        assert(first->vm_proc == second->vm_proc);
        if ((first->vm_end != second->vm_base) ||
-           (first->vm_perm != second->vm_perm) ||
+           (first->vm_prot != second->vm_prot) ||
            (first->vm_flags != second->vm_flags) ||
            (first->vm_file != second->vm_file))
                return -1;
@@ -137,6 +133,24 @@ int merge_vmr(struct vm_region *first, struct vm_region *second)
        return 0;
 }
 
+/* Attempts to merge vmr with adjacent VMRs, returning a ptr to be used for vmr.
+ * It could be the same struct vmr, or possibly another one (usually lower in
+ * the address space. */
+struct vm_region *merge_me(struct vm_region *vmr)
+{
+       struct vm_region *vmr_temp;
+       /* Merge will fail if it cannot do it.  If it succeeds, the second VMR is
+        * destroyed, so we need to be a bit careful. */
+       vmr_temp = TAILQ_PREV(vmr, vmr_tailq, vm_link);
+       if (vmr_temp)
+               if (!merge_vmr(vmr_temp, vmr))
+                       vmr = vmr_temp;
+       vmr_temp = TAILQ_NEXT(vmr, vm_link);
+       if (vmr_temp)
+               merge_vmr(vmr, vmr_temp);
+       return vmr;
+}
+
 /* Grows the vm region up to (and not including) va.  Fails if another is in the
  * way, etc. */
 int grow_vmr(struct vm_region *vmr, uintptr_t va)
@@ -167,7 +181,7 @@ int shrink_vmr(struct vm_region *vmr, uintptr_t va)
 void destroy_vmr(struct vm_region *vmr)
 {
        if (vmr->vm_file)
-               atomic_dec(&vmr->vm_file->f_refcnt);
+               kref_put(&vmr->vm_file->f_kref);
        TAILQ_REMOVE(&vmr->vm_proc->vm_regions, vmr, vm_link);
        kmem_cache_free(vmr_kcache, vmr);
 }
@@ -212,6 +226,15 @@ void isolate_vmrs(struct proc *p, uintptr_t va, size_t len)
                split_vmr(vmr, va + len);
 }
 
+/* Destroys all vmrs of a process - important for when files are mmap()d and
+ * probably later when we share memory regions */
+void destroy_vmrs(struct proc *p)
+{
+       struct vm_region *vm_i;
+       TAILQ_FOREACH(vm_i, &p->vm_regions, vm_link)
+               destroy_vmr(vm_i);
+}
+
 /* This will make new_p have the same VMRs as p, though it does nothing to
  * ensure the physical pages or whatever are shared/mapped/copied/whatever.
  * This is used by fork().
@@ -228,11 +251,11 @@ void duplicate_vmrs(struct proc *p, struct proc *new_p)
                vmr->vm_proc = new_p;
                vmr->vm_base = vm_i->vm_base;
                vmr->vm_end = vm_i->vm_end;
-               vmr->vm_perm = vm_i->vm_perm;   
+               vmr->vm_prot = vm_i->vm_prot;   
                vmr->vm_flags = vm_i->vm_flags; 
+               if (vm_i->vm_file)
+                       kref_get(&vm_i->vm_file->f_kref, 1);
                vmr->vm_file = vm_i->vm_file;
-               if (vmr->vm_file)
-                       atomic_inc(&vmr->vm_file->f_refcnt);
                vmr->vm_foff = vm_i->vm_foff;
                TAILQ_INSERT_TAIL(&new_p->vm_regions, vmr, vm_link);
        }
@@ -244,38 +267,43 @@ void print_vmrs(struct proc *p)
        struct vm_region *vmr;
        printk("VM Regions for proc %d\n", p->pid);
        TAILQ_FOREACH(vmr, &p->vm_regions, vm_link)
-               printk("%02d: (0x%08x - 0x%08x)\n", count++, vmr->vm_base, vmr->vm_end);
+               printk("%02d: (0x%08x - 0x%08x): %08p, %08p, %08p, %08p\n", count++,
+                      vmr->vm_base, vmr->vm_end, vmr->vm_prot, vmr->vm_flags,
+                      vmr->vm_file, vmr->vm_foff);
 }
 
 
+/* Error values aren't quite comprehensive - check man mmap() once we do better
+ * with the FS */
 void *mmap(struct proc *p, uintptr_t addr, size_t len, int prot, int flags,
            int fd, size_t offset)
 {
        struct file *file = NULL;
        printd("mmap(addr %x, len %x, prot %x, flags %x, fd %x, off %x)\n", addr,
               len, prot, flags, fd, offset);
-       if (fd >= 0 && (flags & MAP_SHARED)) {
-               printk("[kernel] mmap() for files requires !MAP_SHARED.\n");
+       if (fd >= 0 && (flags & MAP_ANON)) {
+               set_errno(current_tf, EBADF);
                return MAP_FAILED;
        }
-       if (fd >= 0 && (flags & MAP_ANON)) {
-               printk("[kernel] mmap() with MAP_ANONYMOUS requires fd == -1.\n");
+       if ((addr + len > UMAPTOP) || (PGOFF(addr))) {
+               set_errno(current_tf, EINVAL);
                return MAP_FAILED;
        }
-       if ((flags & MAP_FIXED) && PGOFF(addr)) {
-               printk("[kernel] mmap() page align your addr.\n");
+       if (!len) {
+               set_errno(current_tf, EINVAL);
                return MAP_FAILED;
        }
-       if (!len)
-               return 0;
        if (fd != -1) {
-               file = file_open_from_fd(p, fd);
-               if (!file)
+               file = get_file_from_fd(&p->open_files, fd);
+               if (!file) {
+                       set_errno(current_tf, EBADF);
                        return MAP_FAILED;
+               }
        }
+       addr = MAX(addr, MMAP_LOWEST_VA);
        void *result = do_mmap(p, addr, len, prot, flags, file, offset);
        if (file)
-               file_decref(file);
+               kref_put(&file->f_kref);
        return result;
 }
 
@@ -289,13 +317,13 @@ void *do_mmap(struct proc *p, uintptr_t addr, size_t len, int prot, int flags,
        return ret;
 }
 
-/* Consider moving the top half of this to another function, like mmap(). */
 void *__do_mmap(struct proc *p, uintptr_t addr, size_t len, int prot, int flags,
                 struct file *file, size_t offset)
 {
        len = ROUNDUP(len, PGSIZE);
        int num_pages = len / PGSIZE;
-       struct vm_region *vmr;
+
+       struct vm_region *vmr, *vmr_temp;
 
 #ifndef __CONFIG_DEMAND_PAGING__
        flags |= MAP_POPULATE;
@@ -307,26 +335,36 @@ void *__do_mmap(struct proc *p, uintptr_t addr, size_t len, int prot, int flags,
                __do_munmap(p, addr, len);
        vmr = create_vmr(p, addr, len);
        if (!vmr) {
-               /* not a kernel problem, but i want to know about it */
-               printk("[kernel] mmap() aborted for %08p + %d!\n", addr, len);
+               printk("[kernel] do_mmap() aborted for %08p + %d!\n", addr, len);
+               set_errno(current_tf, ENOMEM);
                return MAP_FAILED;              /* TODO: error propagation for mmap() */
        }
-       vmr->vm_perm = prot;
+       vmr->vm_prot = prot;
        vmr->vm_flags = flags;
+       if (file)
+               kref_get(&file->f_kref, 1);
        vmr->vm_file = file;
        vmr->vm_foff = offset;
-       /* TODO: consider checking to see if we can merge vmrs */
-
-       /* fault in pages now if MAP_POPULATE.  die on failure.  TODO: don't call
-        * destroy like this - you will deadlock.  Also, we want to populate the
-        * region requested, but we ought to be careful and only populate the
-        * requested length and not any merged regions.  doing this by page for now,
-        * though some form of a helper would be nice. */
+       /* Prep the FS to make sure it can mmap the file.  Slightly weird semantics:
+        * they will have a hole in their VM now. */
+       if (file && file->f_op->mmap(file, vmr)) {
+               destroy_vmr(vmr);
+               set_errno(current_tf, EACCES);  /* not quite */
+               return MAP_FAILED;
+       }
+       addr = vmr->vm_base;            /* so we know which pages to populate later */
+       vmr = merge_me(vmr);            /* attempts to merge with neighbors */
+       /* Fault in pages now if MAP_POPULATE - die on failure.  We want to populate
+        * the region requested, but we need to be careful and only populate the
+        * requested length and not any merged regions, which is why we set addr
+        * above and use it here. */
        if (flags & MAP_POPULATE)
                for (int i = 0; i < num_pages; i++)
-                       if (__handle_page_fault(p, vmr->vm_base + i*PGSIZE, vmr->vm_perm))
+                       if (__handle_page_fault(p, addr + i*PGSIZE, vmr->vm_prot)) {
+                               spin_unlock(&p->proc_lock);
                                proc_destroy(p);
-       return (void*SAFE)TC(vmr->vm_base);
+                       }
+       return (void*SAFE)TC(addr);
 }
 
 int mprotect(struct proc *p, uintptr_t addr, size_t len, int prot)
@@ -334,7 +372,7 @@ int mprotect(struct proc *p, uintptr_t addr, size_t len, int prot)
        printd("mprotect(addr %x, len %x, prot %x)\n", addr, len, prot);
        if (!len)
                return 0;
-       if (addr % PGSIZE) {
+       if ((addr % PGSIZE) || (addr < MMAP_LOWEST_VA)) {
                set_errno(current_tf, EINVAL);
                return -1;
        }
@@ -357,17 +395,17 @@ int __do_mprotect(struct proc *p, uintptr_t addr, size_t len, int prot)
        struct vm_region *vmr, *next_vmr;
        pte_t *pte;
        bool shootdown_needed = FALSE;
-       int pte_perm = (prot & PROT_WRITE) ? PTE_USER_RW :
+       int pte_prot = (prot & PROT_WRITE) ? PTE_USER_RW :
                       (prot & (PROT_READ|PROT_EXEC)) ? PTE_USER_RO : 0;
        /* TODO: this is aggressively splitting, when we might not need to if the
-        * perms are the same as the previous.  Plus, there are three excessive
+        * prots are the same as the previous.  Plus, there are three excessive
         * scans.  Finally, we might be able to merge when we are done. */
        isolate_vmrs(p, addr, addr + len);
        vmr = find_first_vmr(p, addr);
        while (vmr && vmr->vm_base < addr + len) {
-               if (vmr->vm_perm == prot)
+               if (vmr->vm_prot == prot)
                        continue;
-               /* if vmr maps a file, then we need to make sure the permission change
+               /* if vmr maps a file, then we need to make sure the protection change
                 * is in compliance with the open mode of the file.  At least for any
                 * mapping that is write-backed to a file.  For now, we just do it for
                 * all file mappings.  And this hasn't been tested */
@@ -377,11 +415,11 @@ int __do_mprotect(struct proc *p, uintptr_t addr, size_t len, int prot)
                                return -1;
                        }
                }
-               vmr->vm_perm = prot;
+               vmr->vm_prot = prot;
                for (uintptr_t va = vmr->vm_base; va < vmr->vm_end; va += PGSIZE) { 
                        pte = pgdir_walk(p->env_pgdir, (void*)va, 0);
                        if (pte && PAGE_PRESENT(*pte)) {
-                               *pte = (*pte & ~PTE_PERM) | pte_perm;
+                               *pte = (*pte & ~PTE_PERM) | pte_prot;
                                shootdown_needed = TRUE;
                        }
                }
@@ -398,7 +436,7 @@ int munmap(struct proc *p, uintptr_t addr, size_t len)
        printd("munmap(addr %x, len %x, prot %x)\n", addr, len, prot);
        if (!len)
                return 0;
-       if (addr % PGSIZE) {
+       if ((addr % PGSIZE) || (addr < MMAP_LOWEST_VA)) {
                set_errno(current_tf, EINVAL);
                return -1;
        }
@@ -474,20 +512,18 @@ int handle_page_fault(struct proc* p, uintptr_t va, int prot)
  * faulted for a different reason (was mprotected on another core), and the
  * shootdown is on its way.  Userspace should have waited for the mprotect to
  * return before trying to write (or whatever), so we don't care and will fault
- * them.
- *
- * We did away with mmapping too much of a file, and will map an entire page, if
- * that file is big enough.  The alternative is to zerofill the last bit if the
- * vmr had a lesser length.  This makes shared mappings and mappings backed by
- * the FS problematic. */
+ * them. */
 int __handle_page_fault(struct proc* p, uintptr_t va, int prot)
 {
        struct vm_region *vmr;
-       /* Check the vmr's permissions */
+       struct page *a_page;
+       unsigned int f_idx;     /* index of the missing page in the file */
+       int retval = 0;
+       /* Check the vmr's protection */
        vmr = find_vmr(p, va);
        if (!vmr)                                                       /* not mapped at all */
                return -EFAULT;
-       if (!(vmr->vm_perm & prot))                     /* wrong perms for this vmr */
+       if (!(vmr->vm_prot & prot))                     /* wrong prots for this vmr */
                return -EFAULT;
        /* find offending PTE (prob don't read this in).  This might alloc an
         * intermediate page table page. */
@@ -504,35 +540,38 @@ int __handle_page_fault(struct proc* p, uintptr_t va, int prot)
                panic("Swapping not supported!");
                return 0;
        }
-       /* allocate a page; maybe zero-fill it */
-       bool zerofill = (vmr->vm_file == NULL);
-       page_t *a_page;
-       if (upage_alloc(p, &a_page, zerofill))
-               return -ENOMEM;
-       /* if this isn't a zero-filled page, read it in from file.  it is the FS's
-        * responsibility to zero out the end of the last page if the EOF is not at
-        * the end of the page.
-        *
-        * TODO: (BLK) doing this while holding the mem lock!  prob want to block
-        * and return to userspace if it's not in the buffer cache.  will want to
-        * set a flag in the vmr so that subsequent faults will know the work is in
-        * progress. */
-       if (!zerofill) {
-               int foffset = ROUNDDOWN(va, PGSIZE) - vmr->vm_base + vmr->vm_foff;
-               int read_len = file_read_page(vmr->vm_file, page2pa(a_page), foffset);
-               if (read_len < 0) {
-                       page_free(a_page);
-                       return read_len;                        /* pass out the error code, for now */
+       if (!vmr->vm_file) {
+               /* No file - just want anonymous memory */
+               if (upage_alloc(p, &a_page, TRUE))
+                       return -ENOMEM;
+       } else {
+               /* Load the file's page in the page cache.
+                * TODO: (BLK) Note, we are holding the mem lock!  We need to rewrite
+                * this stuff so we aren't hold the lock as excessively as we are, and
+                * such that we can block and resume later. */
+               f_idx = (va - vmr->vm_base + vmr->vm_foff) >> PGSHIFT;
+               retval = file_load_page(vmr->vm_file, f_idx, &a_page);
+               if (retval)
+                       return retval;
+               /* If we want a private map that is writable, we'll preemptively give
+                * you a new page.  In the future, we want to CoW this, but the kernel
+                * needs to be able to handle its own page faults first. */
+               if ((vmr->vm_flags |= MAP_PRIVATE) && (vmr->vm_prot |= PROT_WRITE)) {
+                       struct page *cache_page = a_page;
+                       if (upage_alloc(p, &a_page, FALSE))
+                               return -ENOMEM;
+                       memcpy(page2kva(a_page), page2kva(cache_page), PGSIZE);
                }
                /* if this is an executable page, we might have to flush the instruction
                 * cache if our HW requires it. */
-               if (vmr->vm_perm & PROT_EXEC)
+               if (vmr->vm_prot & PROT_EXEC)
                        icache_flush_page((void*)va, page2kva(a_page));
        }
-       /* update the page table */
-       int pte_perm = (vmr->vm_perm & PROT_WRITE) ? PTE_USER_RW :
-                      (vmr->vm_perm & (PROT_READ|PROT_EXEC)) ? PTE_USER_RO : 0;
-       page_incref(a_page);
-       *pte = PTE(page2ppn(a_page), PTE_P | pte_perm);
+       /* update the page table TODO: careful with MAP_PRIVATE etc.  might do this
+        * separately (file, no file) */
+       int pte_prot = (vmr->vm_prot & PROT_WRITE) ? PTE_USER_RW :
+                      (vmr->vm_prot & (PROT_READ|PROT_EXEC)) ? PTE_USER_RO : 0;
+       page_incref(a_page);    /* incref, since we manually insert in the pgdir */
+       *pte = PTE(page2ppn(a_page), PTE_P | pte_prot);
        return 0;
 }