blob: 281da970ef155faffa2a751a4b4bc786e5de2ace [file] [log] [blame]
/*
* linux/mm/mmap.c
*
* Written by obz.
*/
#include <linux/stat.h>
#include <linux/sched.h>
#include <linux/kernel.h>
#include <linux/mm.h>
#include <asm/segment.h>
#include <asm/system.h>
#include <errno.h>
#include <sys/mman.h>
/*
* description of effects of mapping type and prot in current implementation.
* this is due to the current handling of page faults in memory.c. the expected
* behavior is in parens:
*
* map_type prot
* PROT_NONE PROT_READ PROT_WRITE PROT_EXEC
* MAP_SHARED r: (no) yes r: (yes) yes r: (no) yes r: (no) no
* w: (no) yes w: (no) copy w: (yes) yes w: (no) no
* x: (no) no x: (no) no x: (no) no x: (yes) no
*
* MAP_PRIVATE r: (no) yes r: (yes) yes r: (no) yes r: (no) no
* w: (no) copy w: (no) copy w: (copy) copy w: (no) no
* x: (no) no x: (no) no x: (no) no x: (yes) no
*
* the permissions are encoded as cxwr (copy,exec,write,read)
*/
#define MTYP(T) ((T) & MAP_TYPE)
#define PREAD(T,P) (((P) & PROT_READ) ? 1 : 0)
#define PWRITE(T,P) (((P) & PROT_WRITE) ? (MTYP(T) == MAP_SHARED ? 2 : 10) : 0)
#define PEXEC(T,P) (((P) & PROT_EXEC) ? 4 : 0)
#define PERMISS(T,P) (PREAD(T,P)|PWRITE(T,P)|PEXEC(T,P))
#define CODE_SPACE(addr) ((((addr)+4095)&~4095) < \
current->start_code + current->end_code)
static caddr_t
mmap_chr(unsigned long addr, size_t len, int prot, int flags,
struct inode *inode, unsigned long off)
{
int major, minor;
major = MAJOR(inode->i_rdev);
minor = MINOR(inode->i_rdev);
/*
* for character devices, only /dev/mem may be mapped. when the
* swapping code is modified to allow arbitrary sources of pages,
* then we can open it up to regular files.
*/
if (major != 1 || minor != 1)
return (caddr_t)-ENODEV;
/*
* we only allow mappings from address 0 to HIGH_MEMORY, since thats
* the range of our memory [actually this is a lie. the buffer cache
* and ramdisk occupy higher memory, but the paging stuff won't
* let us map to it anyway, so we break it here].
*
* this call is very dangerous! because of the lack of adequate
* tagging of frames, it is possible to mmap over a frame belonging
* to another (innocent) process. with MAP_SHARED|MAP_WRITE, this
* rogue process can trample over the other's data! we ignore this :{
* for now, we hope people will malloc the required amount of space,
* then mmap over it. the mm needs serious work before this can be
* truly useful.
*/
if (len > HIGH_MEMORY || off > HIGH_MEMORY - len) /* avoid overflow */
return (caddr_t)-ENXIO;
if (remap_page_range(addr, off, len, PERMISS(flags, prot)))
return (caddr_t)-EAGAIN;
return (caddr_t)addr;
}
caddr_t
sys_mmap(unsigned long *buffer)
{
unsigned long base, addr;
unsigned long len, limit, off;
int prot, flags, fd;
struct file *file;
struct inode *inode;
addr = (unsigned long) get_fs_long(buffer); /* user address space*/
len = (size_t) get_fs_long(buffer+1); /* nbytes of mapping */
prot = (int) get_fs_long(buffer+2); /* protection */
flags = (int) get_fs_long(buffer+3); /* mapping type */
fd = (int) get_fs_long(buffer+4); /* object to map */
off = (unsigned long) get_fs_long(buffer+5); /* offset in object */
if (fd >= NR_OPEN || fd < 0 || !(file = current->filp[fd]))
return (caddr_t) -EBADF;
if (addr > TASK_SIZE || (addr+(unsigned long) len) > TASK_SIZE)
return (caddr_t) -EINVAL;
inode = file->f_inode;
/*
* do simple checking here so the lower-level routines won't have
* to. we assume access permissions have been handled by the open
* of the memory object, so we don't do any here.
*/
switch (flags & MAP_TYPE) {
case MAP_SHARED:
if ((prot & PROT_WRITE) && !(file->f_mode & 2))
return (caddr_t)-EINVAL;
/* fall through */
case MAP_PRIVATE:
if (!(file->f_mode & 1))
return (caddr_t)-EINVAL;
break;
default:
return (caddr_t)-EINVAL;
}
/*
* obtain the address to map to. we verify (or select) it and ensure
* that it represents a valid section of the address space. we assume
* that if PROT_EXEC is specified this should be in the code segment.
*/
if (prot & PROT_EXEC) {
base = get_base(current->ldt[1]); /* cs */
limit = get_limit(0x0f); /* cs limit */
} else {
base = get_base(current->ldt[2]); /* ds */
limit = get_limit(0x17); /* ds limit */
}
if (flags & MAP_FIXED) {
/*
* if MAP_FIXED is specified, we have to map exactly at this
* address. it must be page aligned and not ambiguous.
*/
if ((addr & 0xfff) || addr > 0x7fffffff || addr == 0 ||
(off & 0xfff))
return (caddr_t)-EINVAL;
if (addr + len > limit)
return (caddr_t)-ENOMEM;
} else {
/*
* we're given a hint as to where to put the address.
* that we still need to search for a range of pages which
* are not mapped and which won't impact the stack or data
* segment.
* in linux, we only have a code segment and data segment.
* since data grows up and stack grows down, we're sort of
* stuck. placing above the data will break malloc, below
* the stack will cause stack overflow. because of this
* we don't allow nonspecified mappings...
*/
return (caddr_t)-ENOMEM;
}
/*
* determine the object being mapped and call the appropriate
* specific mapper. the address has already been validated, but
* not unmapped
*/
if (S_ISCHR(inode->i_mode))
addr = (unsigned long)mmap_chr(base + addr, len, prot, flags,
inode, off);
else
addr = (unsigned long)-ENODEV;
if ((long)addr > 0)
addr -= base;
return (caddr_t)addr;
}
int sys_munmap(unsigned long addr, size_t len)
{
unsigned long base, limit;
base = get_base(current->ldt[2]); /* map into ds */
limit = get_limit(0x17); /* ds limit */
if ((addr & 0xfff) || addr > 0x7fffffff || addr == 0 ||
addr + len > limit)
return -EINVAL;
if (unmap_page_range(base + addr, len))
return -EAGAIN; /* should never happen */
return 0;
}