/* * linux/mm/mmap.c * * Written by obz. */ #include <linux/stat.h> #include <linux/sched.h> #include <linux/kernel.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) extern int remap_page_range(unsigned long from, unsigned long to, unsigned long size, int permiss); extern int unmap_page_range(unsigned long from, unsigned long size); static caddr_t mmap_chr(unsigned long addr, size_t len, int prot, int flags, struct inode *inode, unsigned long off) { int major, minor; extern unsigned long HIGH_MEMORY; 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; }