root/sys/uvm/uvm_glue.c
/*      $OpenBSD: uvm_glue.c,v 1.95 2026/02/11 22:34:40 deraadt Exp $   */
/*      $NetBSD: uvm_glue.c,v 1.44 2001/02/06 19:54:44 eeh Exp $        */

/* 
 * Copyright (c) 1997 Charles D. Cranor and Washington University.
 * Copyright (c) 1991, 1993, The Regents of the University of California.  
 *
 * All rights reserved.
 *
 * This code is derived from software contributed to Berkeley by
 * The Mach Operating System project at Carnegie-Mellon University.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 *      @(#)vm_glue.c   8.6 (Berkeley) 1/5/94
 * from: Id: uvm_glue.c,v 1.1.2.8 1998/02/07 01:16:54 chs Exp
 *
 *
 * Copyright (c) 1987, 1990 Carnegie-Mellon University.
 * All rights reserved.
 * 
 * Permission to use, copy, modify and distribute this software and
 * its documentation is hereby granted, provided that both the copyright
 * notice and this permission notice appear in all copies of the
 * software, derivative works or modified versions, and any portions
 * thereof, and that both notices appear in supporting documentation.
 * 
 * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS "AS IS" 
 * CONDITION.  CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND 
 * FOR ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE.
 * 
 * Carnegie Mellon requests users of this software to return to
 *
 *  Software Distribution Coordinator  or  Software.Distribution@CS.CMU.EDU
 *  School of Computer Science
 *  Carnegie Mellon University
 *  Pittsburgh PA 15213-3890
 *
 * any improvements or extensions that they make and grant Carnegie the
 * rights to redistribute these changes.
 */

/*
 * uvm_glue.c: glue functions
 */

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/proc.h>
#include <sys/resourcevar.h>
#include <sys/buf.h>
#ifdef SYSVSHM
#include <sys/shm.h>
#endif

#include <uvm/uvm.h>

/*
 * uvm_kernacc: can the kernel access a region of memory
 *
 * - called from malloc [DIAGNOSTIC], and /dev/kmem driver (mem.c)
 */
boolean_t
uvm_kernacc(caddr_t addr, size_t len, int rw)
{
        boolean_t rv;
        vaddr_t saddr, eaddr;
        vm_prot_t prot = rw == B_READ ? PROT_READ : PROT_WRITE;

        saddr = trunc_page((vaddr_t)addr);
        eaddr = round_page((vaddr_t)addr + len);
        vm_map_lock_read(kernel_map);
        rv = uvm_map_checkprot(kernel_map, saddr, eaddr, prot);
        vm_map_unlock_read(kernel_map);

        return rv;
}

/*
 * uvm_vslock: wire user memory for I/O
 *
 * - called from sys_sysctl
 */
int
uvm_vslock(struct proc *p, caddr_t addr, size_t len, vm_prot_t access_type)
{
        struct vm_map *map = &p->p_vmspace->vm_map;
        vaddr_t start, end;

        start = trunc_page((vaddr_t)addr);
        end = round_page((vaddr_t)addr + len);
        if (end <= start)
                return (EINVAL);

        return uvm_map_pageable(map, start, end, FALSE, 0);
}

/*
 * uvm_vsunlock: unwire user memory wired by uvm_vslock()
 *
 * - called from sys_sysctl
 */
void
uvm_vsunlock(struct proc *p, caddr_t addr, size_t len)
{
        struct vm_map *map = &p->p_vmspace->vm_map;
        vaddr_t start, end;

        start = trunc_page((vaddr_t)addr);
        end = round_page((vaddr_t)addr + len);
        KASSERT(end > start);

        uvm_map_pageable(map, start, end, TRUE, 0);
}

/*
 * uvm_vslock_device: wire user memory, make sure it's device reachable
 *  and bounce if necessary.
 *
 * - called from physio
 */
int
uvm_vslock_device(struct proc *p, void *addr, size_t len,
    vm_prot_t access_type, void **retp)
{
        struct vm_map *map = &p->p_vmspace->vm_map;
        struct vm_page *pg;
        struct pglist pgl;
        int npages;
        vaddr_t start, end, off;
        vaddr_t sva, va;
        vsize_t sz;
        int error, mapv, i;

        start = trunc_page((vaddr_t)addr);
        end = round_page((vaddr_t)addr + len);
        sz = end - start;
        off = (vaddr_t)addr - start;
        if (end <= start)
                return (EINVAL);

        vm_map_lock_read(map);
retry:
        mapv = map->timestamp;
        vm_map_unlock_read(map);

        if ((error = uvm_fault_wire(map, start, end, access_type)))
                return (error);

        vm_map_lock_read(map);
        if (mapv != map->timestamp)
                goto retry;

        npages = atop(sz);
        for (i = 0; i < npages; i++) {
                paddr_t pa;

                if (!pmap_extract(map->pmap, start + ptoa(i), &pa)) {
                        error = EFAULT;
                        goto out_unwire;
                }
                if (!PADDR_IS_DMA_REACHABLE(pa))
                        break;
        }
        if (i == npages) {
                *retp = NULL;
                return (0);
        }

        va = (vaddr_t)km_alloc(sz, &kv_any, &kp_none, &kd_nowait);
        if (va == 0) {
                error = ENOMEM;
                goto out_unwire;
        }
        sva = va;

        TAILQ_INIT(&pgl);
        error = uvm_pglistalloc(npages * PAGE_SIZE, dma_constraint.ucr_low,
            dma_constraint.ucr_high, 0, 0, &pgl, npages, UVM_PLA_WAITOK);
        if (error)
                goto out_unmap;

        while ((pg = TAILQ_FIRST(&pgl)) != NULL) {
                TAILQ_REMOVE(&pgl, pg, pageq);
                pmap_kenter_pa(va, VM_PAGE_TO_PHYS(pg), PROT_READ | PROT_WRITE);
                va += PAGE_SIZE;
        }
        pmap_update(pmap_kernel());
        KASSERT(va == sva + sz);
        *retp = (void *)(sva + off);

        if ((error = copyin(addr, *retp, len)) == 0)
                return 0;

        uvm_km_pgremove_intrsafe(sva, sva + sz);
        pmap_kremove(sva, sz);
        pmap_update(pmap_kernel());
out_unmap:
        km_free((void *)sva, sz, &kv_any, &kp_none);
out_unwire:
        uvm_fault_unwire_locked(map, start, end);
        vm_map_unlock_read(map);
        return (error);
}

/*
 * uvm_vsunlock_device: unwire user memory wired by uvm_vslock_device()
 *
 * - called from physio
 */
void
uvm_vsunlock_device(struct proc *p, void *addr, size_t len, void *map)
{
        vaddr_t start, end;
        vaddr_t kva;
        vsize_t sz;

        start = trunc_page((vaddr_t)addr);
        end = round_page((vaddr_t)addr + len);
        KASSERT(end > start);
        sz = end - start;

        if (map)
                copyout(map, addr, len);

        uvm_fault_unwire_locked(&p->p_vmspace->vm_map, start, end);
        vm_map_unlock_read(&p->p_vmspace->vm_map);

        if (!map)
                return;

        kva = trunc_page((vaddr_t)map);
        uvm_km_pgremove_intrsafe(kva, kva + sz);
        pmap_kremove(kva, sz);
        pmap_update(pmap_kernel());
        km_free((void *)kva, sz, &kv_any, &kp_none);
}

const struct kmem_va_mode kv_uarea = {
        .kv_map = &kernel_map,
        .kv_align = USPACE_ALIGN
};

/*
 * uvm_uarea_alloc: allocate the u-area for a new thread
 */
vaddr_t
uvm_uarea_alloc(void)
{
        vaddr_t va;

        va = (vaddr_t)km_alloc(USPACE, &kv_uarea, &kp_zero, &kd_waitok);

#ifdef __HAVE_USPACE_GUARD
        /* Carve out a guard page between the PCB and the stack. */
        if (va) {
                struct vm_page *pg = NULL;
                paddr_t pa;

                if (pmap_extract(pmap_kernel(), va + PAGE_SIZE, &pa))
                        pg = PHYS_TO_VM_PAGE(pa);
                pmap_kremove(va + PAGE_SIZE, PAGE_SIZE);
                pmap_update(pmap_kernel());
                if (pg)
                        uvm_pagefree(pg);
        }
#endif

        return va;
}

/*
 * uvm_uarea_free: free a dead thread's stack
 *
 * - the thread passed to us is a dead thread; we
 *   are running on a different context now (the reaper).
 */
void
uvm_uarea_free(struct proc *p)
{
        km_free(p->p_addr, USPACE, &kv_uarea, &kp_zero);
        p->p_addr = NULL;
}

/*
 * uvm_purge: teardown a virtual address space.
 *
 * If multi-threaded, must be called by the last thread of a process.
 */
void
uvm_purge(void)
{
        struct proc *p = curproc;
        struct vmspace *vm = p->p_vmspace;

        KERNEL_ASSERT_UNLOCKED();

#ifdef __HAVE_PMAP_PURGE
        pmap_purge(p);
#endif
        uvmspace_purge(vm);
}

/*
 * uvm_exit: exit a virtual address space
 */
void
uvm_exit(struct process *pr)
{
        struct vmspace *vm = pr->ps_vmspace;

        pr->ps_vmspace = NULL;
        uvmspace_free(vm);
}

/*
 * uvm_init_limit: init per-process VM limits
 *
 * - called for process 0 and then inherited by all others.
 */
void
uvm_init_limits(struct plimit *limit0)
{
        /*
         * Set up the initial limits on process VM.  Set the maximum
         * resident set size to be all of (reasonably) available memory.
         * This causes any single, large process to start random page
         * replacement once it fills memory.
         */
        limit0->pl_rlimit[RLIMIT_STACK].rlim_cur = DFLSSIZ;
        limit0->pl_rlimit[RLIMIT_STACK].rlim_max = MAXSSIZ;
        limit0->pl_rlimit[RLIMIT_DATA].rlim_cur = DFLDSIZ;
        limit0->pl_rlimit[RLIMIT_DATA].rlim_max = MAXDSIZ;
        limit0->pl_rlimit[RLIMIT_RSS].rlim_cur = ptoa(atomic_load_sint(&uvmexp.free));
}

/*
 * uvm_atopg: convert KVAs back to their page structures.
 */
struct vm_page *
uvm_atopg(vaddr_t kva)
{
        struct vm_page *pg;
        paddr_t pa;
        boolean_t rv;
 
        rv = pmap_extract(pmap_kernel(), kva, &pa);
        KASSERT(rv);
        pg = PHYS_TO_VM_PAGE(pa);
        KASSERT(pg != NULL);
        return (pg);
}

#ifndef SMALL_KERNEL
int
fill_vmmap(struct process *pr, struct kinfo_vmentry *kve,
    size_t *lenp)
{
        struct vm_map *map;

        if (pr != NULL)
                map = &pr->ps_vmspace->vm_map;
        else
                map = kernel_map;
        return uvm_map_fill_vmmap(map, kve, lenp);
}
#endif