/* Memory management system calls and support
   Copyright (C) 1991 Free Software Foundation

This file is part of the GNU Hurd.

The GNU Hurd is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 1, or (at your option)
any later version.

The GNU Hurd is distributed in the hope that it will be useful, 
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with the GNU Hurd; see the file COPYING.  If not, write to
the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.  */

/* Written by Michael I. Bushnell.  */

#include "mach.h"
#include <cthreads.h>
#include <gnu/types.h>
#include <gnu/param.h>
#include <gnu/errno.h>
#include <gnu/mman.h>
#include <gnu/signal.h>
#include <hurd/hurd_types.h>

#include "io.h"

#include "alix.h"
#include <machine/alix_machdep.h>
#include "alix_io.h"

/* Missing:
	msync, madvise, mincore, mset, mclear, msleep, mwakeup, vadvise

msync could be done with an RPC to the I/O object, but kernel
support is really what's needed.

mincore and madvise require kernel support.

*/
	
static struct mutex mmanlock;

/* Current memory situation */

static vm_address_t databreak;	/* current break */
static vm_address_t stackbreak;	/* current stack break */

static vm_address_t databreakpg; /* page limit of current break */
static vm_address_t stackbreakpg; /* page limit of current stack break */

static vm_address_t maxdalloc;	/* amount of address space allocated data */
static vm_address_t maxsalloc;	/* amount of address space allocated stack */

static vm_address_t data_limit;	/* current data resource limit */
static vm_address_t stack_limit; /* current stack resource limit */

/* These values are used in the following way:

   databreak is maintained for the user's convenience.
   all memory from zero to databreakpg is allocated and usable.
   all memory from databreakpg to maxdalloc is allocated but unusable.
   the data_limit is a limit for the growth of databreak.

The stack segment is maintained analagously. */

int
sbrk1 (int incr, int *ret1)
{
  vm_address_t newdatabreakpg;

  if (databreak + incr >= data_limit)
    {
      mutex_unlock (&mmanlock);
      return ENOMEM;
    }
  
  if (databreak + incr <= databreakpg)
    {
      *ret1 = (int)(databreak += incr);
      mutex_unlock (&mmanlock);
      return 0;
    }
  
  newdatabreakpg = round_page (databreak + incr);
  
  if (newdatabreakpg <= maxdalloc)
    {
      vm_protect (mach_task_self (), databreakpg, newdatabreakpg - databreakpg,
		  0, VM_PROT_READ | VM_PROT_WRITE | VM_PROT_EXECUTE);
      databreakpg = newdatabreakpg;
      *ret1 = (int)(databreak += incr);
      mutex_unlock (&mmanlock);
      return 0;
    }

  mutex_unlock (&mmanlock);
  return ENOMEM;
}

/* SYSCALL: Set the current "data segment" size. */
int
brk (void *ap, int *ret1, int *ret2)
{
  struct
    {
      int newbrk;
    }
  *args = ap;

  mutex_lock (&mmanlock);

  if (args->newbrk <= databreak)
    {
      *ret1 = databreak;
      mutex_unlock (&mmanlock);
      return 0;
    }
      
  return sbrk1 (args->newbrk - databreak, ret1);
}  

/* SYSCALL: Change the current "data segment" size.  */
int
sbrk (void *ap, int *ret1, int *ret2)
{
  struct
    {
      int incr;
    }
  *args = ap;
  
  if (args->incr <= 0)
    {
      /* XXX */
      mutex_lock (&mmanlock);
      *ret1 = databreak;
      mutex_unlock (&mmanlock);
      return 0;
    }

  mutex_lock (&mmanlock);

  return sbrk1 (args->incr, ret1);
}

/* Change the current stack segment size.  */
int
sstk1 (int incr,
       int *ret)
{
  vm_address_t newstackbreakpg;
  
#ifdef STACK_DOWN
  if (stackbreak - incr <= stack_limit)
#else
  if (stackbreak + incr >= stack_limit)
#endif
    {
      mutex_unlock (&mmanlock);
      return ENOMEM;
    }
  
#ifdef STACK_DOWN
  if (stackbreak - incr >= stackbreakpg)
#else
  if (stackbreak + incr <= stackbreakpg)
#endif
    {
#ifdef STACK_DOWN
      *ret = stackbreak -= incr;
#else
      *ret = stackbreak += incr;
#endif
      mutex_unlock (&mmanlock);
      return 0;
    }
  
#ifdef STACK_DOWN
  newstackbreakpg = round_page (stackbreak - incr);
#else
  newstackbreakpg = route_page (stackbreak + incr);
#endif
  
#ifdef STACK_DOWN
  if (newstackbreakpg >= maxsalloc)
#else
  if (newstackbreakpg <= maxsalloc)
#endif
    {
      vm_protect (mach_task_self (),
#ifdef STACK_DOWN
		  newstackbreakpg, stackbreakpg - newstackbreakpg,
#else
		  stackbreakpg, newstackbreakpg - stackbreakpg,
#endif
		  0, VM_PROT_READ | VM_PROT_WRITE | VM_PROT_EXECUTE);
      stackbreakpg = newstackbreakpg;
#ifdef STACK_DOWN
      *ret = stackbreak -= incr;
#else
      *ret = stackbreak += incr;
#endif
      mutex_unlock (&mmanlock);
      return 0;
    }
  
  mutex_unlock (&mmanlock);
  return ENOMEM;
}


/* SYSCALL: Change the current "stack segment" size.  */
int sstk (void *ap, int *ret1, int *ret2)
{
  struct
    {
      int incr;
    }
  *args = ap;

  if (args->incr <= 0)
    {
      /* XXX */
      mutex_lock (&mmanlock);
      *ret1 = stackbreak;
      mutex_unlock (&mmanlock);
      return 0;
    }
  
  mutex_lock (&mmanlock);

  return sstk1 (args->incr, ret1);
}

/* Handle a stack fault.  Return 1 if we could extend the stack to
   cover the faulting address.  */
int
stack_fault (vm_address_t addr)
{
  int incr;
  int err;
  int foo;
  
  mutex_lock (&mmanlock);
  
#ifdef STACK_DOWN
  if (addr < stack_limit || addr > stackbreak)
#else
  if (addr > stack_limit || addr < stackbreak)
#endif
    {
      mutex_unlock (&mmanlock);
      return 0;
    }
  
#ifdef STACK_DOWN
  incr = stackbreak - addr;
#else
  incr = addr - stackbreak;
#endif
  
  err = sstk1 (incr, &foo);
  return !err;
}


/* SYSCALL: Return the pagesize of the machine.  */
int
getpagesize (void *ap, int *ret1, int *ret2)
{
  *ret1 = vm_page_size;
  return 0;
}


/* Change this file's knowledge of the RLIM_DATA resource limit.  */
int
increase_dalloc (int newmax,
		 int newlimit)
{
  error_t err;
  
  newmax = round_page (newmax);

  mutex_lock (&mmanlock);

  data_limit = newlimit;

  if (newmax <= maxdalloc)
    {
      mutex_unlock (&mmanlock);
      return 0;
    }

  /* We use vm_map here to guarantee no race between allocation and
     permission alteration.  */
  err = vm_map (mach_task_self (), &maxdalloc, newmax - maxdalloc, 0, 0, 
		MEMORY_OBJECT_NULL, 0, 0, 0, 0, VM_INHERIT_SHARE);
  if (err != KERN_SUCCESS)
    {
      mutex_unlock (&mmanlock);
      return ENOMEM;
    }

  maxdalloc = newmax;

  mutex_unlock (&mmanlock);
  return 0;
}


/* Change this file's knowledge of the RLIM_STACK resource limit.  */
int
increase_salloc (int newmax,
		 int newlimit)
{
  error_t err;
  
  newmax = round_page (newmax);
  
  mutex_lock (&mmanlock);

#ifdef STACK_DOWN
  if (newmax > maxsalloc)
#else
  if (newmax < maxsalloc)
#endif
    {
      stack_limit = newlimit;
      mutex_unlock (&mmanlock);
      return 0;
    }
  
  /* We use vm_map here for the same reason as in increase_dalloc.  */
#ifdef STACK_DOWN
  err = vm_map (mach_task_self (), (u_int *)&newmax, maxsalloc - newmax, 
		0, 0, MEMORY_OBJECT_NULL, 0, 0, 0, 0, VM_INHERIT_SHARE);
#else
  err = vm_map (mach_task_self (), (u_int *)&maxsalloc, newmax - maxsalloc,
		0, 0, MEMORY_OBJECT_NULL, 0, 0, 0, 0, VM_INHERIT_SHARE);
#endif
  if (err != KERN_SUCCESS)
    {
      mutex_unlock (&mmanlock);
      return ENOMEM;
    }
  
  stack_limit = newlimit;
  maxsalloc = newmax;

  mutex_unlock (&mmanlock);
  return 0;
}

/* SYSCALL: Map from a file descriptor into the process's address space.  */
int
mmap (void *ap, int *ret1, int *ret2)
{
  struct
    {
      caddr_t addr;
      int *len;
      int prot;
      int flags;
      int fd;
      off_t pos;
    }
  *args = ap;

  memory_object_t memobj;
  memory_object_t unused1, unused2;
  struct filed *f;
  error_t err;
  int mapaddr;
  int machprot;
  int inlen;
  
  if (err = copyin (args->len, &inlen, sizeof (int)))
    return err;
  
  if ((int)args->addr % vm_page_size
      || inlen % vm_page_size
      || args->pos % vm_page_size)
    return EINVAL;
  
  if (args->flags & MAP_INHERIT)
    return EINVAL;

  mutex_lock (&iolock);
  if (args->fd < 0 || args->fd >= dtablesize || !dtable[args->fd].ioserver)
    {
      mutex_unlock (&iolock);
      return EBADF;
    }
  f = &dtable[args->fd];
  
  err = io_map (f->ioserver, &memobj, &unused1, &unused2);
  mutex_unlock (&iolock);

  mutex_lock (&mmanlock);

  /* At some point we should allow mapping into areas in the
     allocated-but-unused portion of the data or stack segment, and then
     the relevant portion will need to be deallocated before the vm_map.
     */
  
  if (!err)
    {
      if (unused1)
	mach_port_deallocate (mach_task_self (), unused1);
      if (unused2)
	mach_port_deallocate (mach_task_self (), unused2);
    }
  if (err || memobj == MACH_PORT_NULL)
    {
      mutex_unlock (&mmanlock);
      if (!err)
	err = EOPNOTSUPP;
      else
	err = h2ae (err);
      return err;
    }
  
  mapaddr = (int)args->addr;
  machprot = (args->prot & PROT_READ) ? VM_PROT_READ : 0;
  machprot |= (args->prot & PROT_WRITE) ? VM_PROT_WRITE : 0;
  machprot |= (args->prot & PROT_EXEC) ? VM_PROT_EXECUTE : 0;
  
  err = vm_map (mach_task_self (), (u_int *)&mapaddr, inlen, 0,
		!(args->flags & MAP_FIXED), memobj, args->pos,
		!(args->flags & MAP_SHARED), machprot, 
		VM_PROT_READ|VM_PROT_WRITE|VM_PROT_EXECUTE,
		(args->flags & MAP_SHARED)
		? VM_INHERIT_SHARE : VM_INHERIT_COPY);
  mach_port_deallocate (mach_task_self (), memobj);
  mutex_unlock (&mmanlock);
  if (err)
    return h2ae (err);

  *ret1 = mapaddr;
  return 0;
}

/* SYSCALL: Mark a portion of the process address space as unmapped.  */
int
munmap (void *ap, int *ret1, int *ret2)
{
  struct
    {
      caddr_t addr;
      int len;
    }
  *args = ap;

  if ((int)args->addr % vm_page_size || args->len % vm_page_size)
    return EINVAL;
  
  mutex_lock (&mmanlock);
  
  if ((int)args->addr <= maxdalloc
#ifdef STACK_DOWN
      || (int)args->addr >= maxsalloc
#else
      || (int)args->addr <= maxsalloc /* XXX this is clearly wrong. */
#endif
      )
    {
      mutex_unlock (&mmanlock);
      return EINVAL;
    }
  
  vm_deallocate (mach_task_self (), (int)args->addr, args->len);

  mutex_unlock (&mmanlock);
  return 0;
}

/* SYSCALL: Change the current protection on a portion of the
   process's address space.  */
int
mprotect (void *ap, int *ret1, int *ret2)
{
  struct
    {
      caddr_t addr;
      int len;
      int prot;
    }
  *args = ap;
  int machprot;

  if ((int)args->addr % vm_page_size || args->len % vm_page_size)
    return EINVAL;
  
  machprot = (args->prot & PROT_READ) ? VM_PROT_READ : 0;
  machprot |= (args->prot & PROT_WRITE) ? VM_PROT_WRITE : 0;
  machprot |= (args->prot & PROT_EXEC) ? VM_PROT_EXECUTE : 0;
  
  vm_protect (mach_task_self (), (int)args->addr, args->len, 0, machprot);
  return 0;
}

