| /* |
| * 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; |
| } |