154936004Sbellard /* 254936004Sbellard * mmap support for qemu 354936004Sbellard * 454936004Sbellard * Copyright (c) 2003 Fabrice Bellard 554936004Sbellard * 654936004Sbellard * This program is free software; you can redistribute it and/or modify 754936004Sbellard * it under the terms of the GNU General Public License as published by 854936004Sbellard * the Free Software Foundation; either version 2 of the License, or 954936004Sbellard * (at your option) any later version. 1054936004Sbellard * 1154936004Sbellard * This program is distributed in the hope that it will be useful, 1254936004Sbellard * but WITHOUT ANY WARRANTY; without even the implied warranty of 1354936004Sbellard * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 1454936004Sbellard * GNU General Public License for more details. 1554936004Sbellard * 1654936004Sbellard * You should have received a copy of the GNU General Public License 1754936004Sbellard * along with this program; if not, write to the Free Software 1854936004Sbellard * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 1954936004Sbellard */ 2054936004Sbellard #include <stdlib.h> 2154936004Sbellard #include <stdio.h> 2254936004Sbellard #include <stdarg.h> 2354936004Sbellard #include <string.h> 2454936004Sbellard #include <unistd.h> 2554936004Sbellard #include <errno.h> 2654936004Sbellard #include <sys/mman.h> 2754936004Sbellard 2854936004Sbellard #include "qemu.h" 2954936004Sbellard 3054936004Sbellard //#define DEBUG_MMAP 3154936004Sbellard 3254936004Sbellard /* NOTE: all the constants are the HOST ones */ 3354936004Sbellard int target_mprotect(unsigned long start, unsigned long len, int prot) 3454936004Sbellard { 3554936004Sbellard unsigned long end, host_start, host_end, addr; 3654936004Sbellard int prot1, ret; 3754936004Sbellard 3854936004Sbellard #ifdef DEBUG_MMAP 3954936004Sbellard printf("mprotect: start=0x%lx len=0x%lx prot=%c%c%c\n", start, len, 4054936004Sbellard prot & PROT_READ ? 'r' : '-', 4154936004Sbellard prot & PROT_WRITE ? 'w' : '-', 4254936004Sbellard prot & PROT_EXEC ? 'x' : '-'); 4354936004Sbellard #endif 4454936004Sbellard 4554936004Sbellard if ((start & ~TARGET_PAGE_MASK) != 0) 4654936004Sbellard return -EINVAL; 4754936004Sbellard len = TARGET_PAGE_ALIGN(len); 4854936004Sbellard end = start + len; 4954936004Sbellard if (end < start) 5054936004Sbellard return -EINVAL; 5154936004Sbellard if (prot & ~(PROT_READ | PROT_WRITE | PROT_EXEC)) 5254936004Sbellard return -EINVAL; 5354936004Sbellard if (len == 0) 5454936004Sbellard return 0; 5554936004Sbellard 56*83fb7adfSbellard host_start = start & qemu_host_page_mask; 5754936004Sbellard host_end = HOST_PAGE_ALIGN(end); 5854936004Sbellard if (start > host_start) { 5954936004Sbellard /* handle host page containing start */ 6054936004Sbellard prot1 = prot; 6154936004Sbellard for(addr = host_start; addr < start; addr += TARGET_PAGE_SIZE) { 6254936004Sbellard prot1 |= page_get_flags(addr); 6354936004Sbellard } 64*83fb7adfSbellard if (host_end == host_start + qemu_host_page_size) { 65d418c81eSbellard for(addr = end; addr < host_end; addr += TARGET_PAGE_SIZE) { 66d418c81eSbellard prot1 |= page_get_flags(addr); 67d418c81eSbellard } 68d418c81eSbellard end = host_end; 69d418c81eSbellard } 70*83fb7adfSbellard ret = mprotect((void *)host_start, qemu_host_page_size, prot1 & PAGE_BITS); 7154936004Sbellard if (ret != 0) 7254936004Sbellard return ret; 73*83fb7adfSbellard host_start += qemu_host_page_size; 7454936004Sbellard } 7554936004Sbellard if (end < host_end) { 7654936004Sbellard prot1 = prot; 7754936004Sbellard for(addr = end; addr < host_end; addr += TARGET_PAGE_SIZE) { 7854936004Sbellard prot1 |= page_get_flags(addr); 7954936004Sbellard } 80*83fb7adfSbellard ret = mprotect((void *)(host_end - qemu_host_page_size), qemu_host_page_size, 8154936004Sbellard prot1 & PAGE_BITS); 8254936004Sbellard if (ret != 0) 8354936004Sbellard return ret; 84*83fb7adfSbellard host_end -= qemu_host_page_size; 8554936004Sbellard } 8654936004Sbellard 8754936004Sbellard /* handle the pages in the middle */ 8854936004Sbellard if (host_start < host_end) { 8954936004Sbellard ret = mprotect((void *)host_start, host_end - host_start, prot); 9054936004Sbellard if (ret != 0) 9154936004Sbellard return ret; 9254936004Sbellard } 9354936004Sbellard page_set_flags(start, start + len, prot | PAGE_VALID); 9454936004Sbellard return 0; 9554936004Sbellard } 9654936004Sbellard 9754936004Sbellard /* map an incomplete host page */ 9854936004Sbellard int mmap_frag(unsigned long host_start, 9954936004Sbellard unsigned long start, unsigned long end, 10054936004Sbellard int prot, int flags, int fd, unsigned long offset) 10154936004Sbellard { 10254936004Sbellard unsigned long host_end, ret, addr; 10354936004Sbellard int prot1, prot_new; 10454936004Sbellard 105*83fb7adfSbellard host_end = host_start + qemu_host_page_size; 10654936004Sbellard 10754936004Sbellard /* get the protection of the target pages outside the mapping */ 10854936004Sbellard prot1 = 0; 10954936004Sbellard for(addr = host_start; addr < host_end; addr++) { 11054936004Sbellard if (addr < start || addr >= end) 11154936004Sbellard prot1 |= page_get_flags(addr); 11254936004Sbellard } 11354936004Sbellard 11454936004Sbellard if (prot1 == 0) { 11554936004Sbellard /* no page was there, so we allocate one */ 116*83fb7adfSbellard ret = (long)mmap((void *)host_start, qemu_host_page_size, prot, 11754936004Sbellard flags | MAP_ANONYMOUS, -1, 0); 11854936004Sbellard if (ret == -1) 11954936004Sbellard return ret; 12054936004Sbellard } 12154936004Sbellard prot1 &= PAGE_BITS; 12254936004Sbellard 12354936004Sbellard prot_new = prot | prot1; 12454936004Sbellard if (!(flags & MAP_ANONYMOUS)) { 12554936004Sbellard /* msync() won't work here, so we return an error if write is 12654936004Sbellard possible while it is a shared mapping */ 12754936004Sbellard if ((flags & MAP_TYPE) == MAP_SHARED && 12854936004Sbellard (prot & PROT_WRITE)) 12954936004Sbellard return -EINVAL; 13054936004Sbellard 13154936004Sbellard /* adjust protection to be able to read */ 13254936004Sbellard if (!(prot1 & PROT_WRITE)) 133*83fb7adfSbellard mprotect((void *)host_start, qemu_host_page_size, prot1 | PROT_WRITE); 13454936004Sbellard 13554936004Sbellard /* read the corresponding file data */ 13654936004Sbellard pread(fd, (void *)start, end - start, offset); 13754936004Sbellard 13854936004Sbellard /* put final protection */ 13954936004Sbellard if (prot_new != (prot1 | PROT_WRITE)) 140*83fb7adfSbellard mprotect((void *)host_start, qemu_host_page_size, prot_new); 14154936004Sbellard } else { 14254936004Sbellard /* just update the protection */ 14354936004Sbellard if (prot_new != prot1) { 144*83fb7adfSbellard mprotect((void *)host_start, qemu_host_page_size, prot_new); 14554936004Sbellard } 14654936004Sbellard } 14754936004Sbellard return 0; 14854936004Sbellard } 14954936004Sbellard 15054936004Sbellard /* NOTE: all the constants are the HOST ones */ 15154936004Sbellard long target_mmap(unsigned long start, unsigned long len, int prot, 15254936004Sbellard int flags, int fd, unsigned long offset) 15354936004Sbellard { 15454936004Sbellard unsigned long ret, end, host_start, host_end, retaddr, host_offset, host_len; 1554f2ac237Sbellard #if defined(__alpha__) || defined(__sparc__) || defined(__x86_64__) 1564f2ac237Sbellard static unsigned long last_start = 0x40000000; 1574f2ac237Sbellard #endif 15854936004Sbellard 15954936004Sbellard #ifdef DEBUG_MMAP 16054936004Sbellard { 16154936004Sbellard printf("mmap: start=0x%lx len=0x%lx prot=%c%c%c flags=", 16254936004Sbellard start, len, 16354936004Sbellard prot & PROT_READ ? 'r' : '-', 16454936004Sbellard prot & PROT_WRITE ? 'w' : '-', 16554936004Sbellard prot & PROT_EXEC ? 'x' : '-'); 16654936004Sbellard if (flags & MAP_FIXED) 16754936004Sbellard printf("MAP_FIXED "); 16854936004Sbellard if (flags & MAP_ANONYMOUS) 16954936004Sbellard printf("MAP_ANON "); 17054936004Sbellard switch(flags & MAP_TYPE) { 17154936004Sbellard case MAP_PRIVATE: 17254936004Sbellard printf("MAP_PRIVATE "); 17354936004Sbellard break; 17454936004Sbellard case MAP_SHARED: 17554936004Sbellard printf("MAP_SHARED "); 17654936004Sbellard break; 17754936004Sbellard default: 17854936004Sbellard printf("[MAP_TYPE=0x%x] ", flags & MAP_TYPE); 17954936004Sbellard break; 18054936004Sbellard } 18154936004Sbellard printf("fd=%d offset=%lx\n", fd, offset); 18254936004Sbellard } 18354936004Sbellard #endif 18454936004Sbellard 18554936004Sbellard if (offset & ~TARGET_PAGE_MASK) 18654936004Sbellard return -EINVAL; 18754936004Sbellard 18854936004Sbellard len = TARGET_PAGE_ALIGN(len); 18954936004Sbellard if (len == 0) 19054936004Sbellard return start; 191*83fb7adfSbellard host_start = start & qemu_host_page_mask; 19254936004Sbellard 19354936004Sbellard if (!(flags & MAP_FIXED)) { 194bc51c5c9Sbellard #if defined(__alpha__) || defined(__sparc__) || defined(__x86_64__) 195917f95fdSbellard /* tell the kenel to search at the same place as i386 */ 1964f2ac237Sbellard if (host_start == 0) { 1974f2ac237Sbellard host_start = last_start; 1984f2ac237Sbellard last_start += HOST_PAGE_ALIGN(len); 1994f2ac237Sbellard } 200917f95fdSbellard #endif 201*83fb7adfSbellard if (qemu_host_page_size != qemu_real_host_page_size) { 20254936004Sbellard /* NOTE: this code is only for debugging with '-p' option */ 20354936004Sbellard /* reserve a memory area */ 204*83fb7adfSbellard host_len = HOST_PAGE_ALIGN(len) + qemu_host_page_size - TARGET_PAGE_SIZE; 20554936004Sbellard host_start = (long)mmap((void *)host_start, host_len, PROT_NONE, 20654936004Sbellard MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); 20754936004Sbellard if (host_start == -1) 20854936004Sbellard return host_start; 20954936004Sbellard host_end = host_start + host_len; 21054936004Sbellard start = HOST_PAGE_ALIGN(host_start); 21154936004Sbellard end = start + HOST_PAGE_ALIGN(len); 21254936004Sbellard if (start > host_start) 21354936004Sbellard munmap((void *)host_start, start - host_start); 21454936004Sbellard if (end < host_end) 21554936004Sbellard munmap((void *)end, host_end - end); 21654936004Sbellard /* use it as a fixed mapping */ 21754936004Sbellard flags |= MAP_FIXED; 21854936004Sbellard } else { 21954936004Sbellard /* if not fixed, no need to do anything */ 220*83fb7adfSbellard host_offset = offset & qemu_host_page_mask; 22154936004Sbellard host_len = len + offset - host_offset; 22254936004Sbellard start = (long)mmap((void *)host_start, host_len, 22354936004Sbellard prot, flags, fd, host_offset); 22454936004Sbellard if (start == -1) 22554936004Sbellard return start; 22654936004Sbellard /* update start so that it points to the file position at 'offset' */ 22754936004Sbellard if (!(flags & MAP_ANONYMOUS)) 22854936004Sbellard start += offset - host_offset; 22954936004Sbellard goto the_end1; 23054936004Sbellard } 23154936004Sbellard } 23254936004Sbellard 23354936004Sbellard if (start & ~TARGET_PAGE_MASK) 23454936004Sbellard return -EINVAL; 23554936004Sbellard end = start + len; 23654936004Sbellard host_end = HOST_PAGE_ALIGN(end); 23754936004Sbellard 23854936004Sbellard /* worst case: we cannot map the file because the offset is not 23954936004Sbellard aligned, so we read it */ 24054936004Sbellard if (!(flags & MAP_ANONYMOUS) && 241*83fb7adfSbellard (offset & ~qemu_host_page_mask) != (start & ~qemu_host_page_mask)) { 24254936004Sbellard /* msync() won't work here, so we return an error if write is 24354936004Sbellard possible while it is a shared mapping */ 24454936004Sbellard if ((flags & MAP_TYPE) == MAP_SHARED && 24554936004Sbellard (prot & PROT_WRITE)) 24654936004Sbellard return -EINVAL; 24754936004Sbellard retaddr = target_mmap(start, len, prot | PROT_WRITE, 24854936004Sbellard MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS, 24954936004Sbellard -1, 0); 25054936004Sbellard if (retaddr == -1) 25154936004Sbellard return retaddr; 25254936004Sbellard pread(fd, (void *)start, len, offset); 25354936004Sbellard if (!(prot & PROT_WRITE)) { 25454936004Sbellard ret = target_mprotect(start, len, prot); 25554936004Sbellard if (ret != 0) 25654936004Sbellard return ret; 25754936004Sbellard } 25854936004Sbellard goto the_end; 25954936004Sbellard } 26054936004Sbellard 26154936004Sbellard /* handle the start of the mapping */ 26254936004Sbellard if (start > host_start) { 263*83fb7adfSbellard if (host_end == host_start + qemu_host_page_size) { 26454936004Sbellard /* one single host page */ 26554936004Sbellard ret = mmap_frag(host_start, start, end, 26654936004Sbellard prot, flags, fd, offset); 26754936004Sbellard if (ret == -1) 26854936004Sbellard return ret; 26954936004Sbellard goto the_end1; 27054936004Sbellard } 271*83fb7adfSbellard ret = mmap_frag(host_start, start, host_start + qemu_host_page_size, 27254936004Sbellard prot, flags, fd, offset); 27354936004Sbellard if (ret == -1) 27454936004Sbellard return ret; 275*83fb7adfSbellard host_start += qemu_host_page_size; 27654936004Sbellard } 27754936004Sbellard /* handle the end of the mapping */ 27854936004Sbellard if (end < host_end) { 279*83fb7adfSbellard ret = mmap_frag(host_end - qemu_host_page_size, 280*83fb7adfSbellard host_end - qemu_host_page_size, host_end, 28154936004Sbellard prot, flags, fd, 282*83fb7adfSbellard offset + host_end - qemu_host_page_size - start); 28354936004Sbellard if (ret == -1) 28454936004Sbellard return ret; 285*83fb7adfSbellard host_end -= qemu_host_page_size; 28654936004Sbellard } 28754936004Sbellard 28854936004Sbellard /* map the middle (easier) */ 28954936004Sbellard if (host_start < host_end) { 2904a585ccbSbellard unsigned long offset1; 2914a585ccbSbellard if (flags & MAP_ANONYMOUS) 2924a585ccbSbellard offset1 = 0; 2934a585ccbSbellard else 2944a585ccbSbellard offset1 = offset + host_start - start; 29554936004Sbellard ret = (long)mmap((void *)host_start, host_end - host_start, 2964a585ccbSbellard prot, flags, fd, offset1); 29754936004Sbellard if (ret == -1) 29854936004Sbellard return ret; 29954936004Sbellard } 30054936004Sbellard the_end1: 30154936004Sbellard page_set_flags(start, start + len, prot | PAGE_VALID); 30254936004Sbellard the_end: 30354936004Sbellard #ifdef DEBUG_MMAP 304917f95fdSbellard printf("ret=0x%lx\n", (long)start); 30554936004Sbellard page_dump(stdout); 30654936004Sbellard printf("\n"); 30754936004Sbellard #endif 30854936004Sbellard return start; 30954936004Sbellard } 31054936004Sbellard 31154936004Sbellard int target_munmap(unsigned long start, unsigned long len) 31254936004Sbellard { 31354936004Sbellard unsigned long end, host_start, host_end, addr; 31454936004Sbellard int prot, ret; 31554936004Sbellard 31654936004Sbellard #ifdef DEBUG_MMAP 31754936004Sbellard printf("munmap: start=0x%lx len=0x%lx\n", start, len); 31854936004Sbellard #endif 31954936004Sbellard if (start & ~TARGET_PAGE_MASK) 32054936004Sbellard return -EINVAL; 32154936004Sbellard len = TARGET_PAGE_ALIGN(len); 32254936004Sbellard if (len == 0) 32354936004Sbellard return -EINVAL; 32454936004Sbellard end = start + len; 325*83fb7adfSbellard host_start = start & qemu_host_page_mask; 32654936004Sbellard host_end = HOST_PAGE_ALIGN(end); 32754936004Sbellard 32854936004Sbellard if (start > host_start) { 32954936004Sbellard /* handle host page containing start */ 33054936004Sbellard prot = 0; 33154936004Sbellard for(addr = host_start; addr < start; addr += TARGET_PAGE_SIZE) { 33254936004Sbellard prot |= page_get_flags(addr); 33354936004Sbellard } 334*83fb7adfSbellard if (host_end == host_start + qemu_host_page_size) { 335d418c81eSbellard for(addr = end; addr < host_end; addr += TARGET_PAGE_SIZE) { 336d418c81eSbellard prot |= page_get_flags(addr); 337d418c81eSbellard } 338d418c81eSbellard end = host_end; 339d418c81eSbellard } 34054936004Sbellard if (prot != 0) 341*83fb7adfSbellard host_start += qemu_host_page_size; 34254936004Sbellard } 34354936004Sbellard if (end < host_end) { 34454936004Sbellard prot = 0; 34554936004Sbellard for(addr = end; addr < host_end; addr += TARGET_PAGE_SIZE) { 34654936004Sbellard prot |= page_get_flags(addr); 34754936004Sbellard } 34854936004Sbellard if (prot != 0) 349*83fb7adfSbellard host_end -= qemu_host_page_size; 35054936004Sbellard } 35154936004Sbellard 35254936004Sbellard /* unmap what we can */ 35354936004Sbellard if (host_start < host_end) { 35454936004Sbellard ret = munmap((void *)host_start, host_end - host_start); 35554936004Sbellard if (ret != 0) 35654936004Sbellard return ret; 35754936004Sbellard } 35854936004Sbellard 35954936004Sbellard page_set_flags(start, start + len, 0); 36054936004Sbellard return 0; 36154936004Sbellard } 36254936004Sbellard 36354936004Sbellard /* XXX: currently, we only handle MAP_ANONYMOUS and not MAP_FIXED 36454936004Sbellard blocks which have been allocated starting on a host page */ 36554936004Sbellard long target_mremap(unsigned long old_addr, unsigned long old_size, 36654936004Sbellard unsigned long new_size, unsigned long flags, 36754936004Sbellard unsigned long new_addr) 36854936004Sbellard { 36954936004Sbellard int prot; 37054936004Sbellard 37154936004Sbellard /* XXX: use 5 args syscall */ 37254936004Sbellard new_addr = (long)mremap((void *)old_addr, old_size, new_size, flags); 37354936004Sbellard if (new_addr == -1) 37454936004Sbellard return new_addr; 37554936004Sbellard prot = page_get_flags(old_addr); 37654936004Sbellard page_set_flags(old_addr, old_addr + old_size, 0); 37754936004Sbellard page_set_flags(new_addr, new_addr + new_size, prot | PAGE_VALID); 37854936004Sbellard return new_addr; 37954936004Sbellard } 38054936004Sbellard 38154936004Sbellard int target_msync(unsigned long start, unsigned long len, int flags) 38254936004Sbellard { 38354936004Sbellard unsigned long end; 38454936004Sbellard 38554936004Sbellard if (start & ~TARGET_PAGE_MASK) 38654936004Sbellard return -EINVAL; 38754936004Sbellard len = TARGET_PAGE_ALIGN(len); 38854936004Sbellard end = start + len; 389d418c81eSbellard if (end < start) 390d418c81eSbellard return -EINVAL; 391d418c81eSbellard if (end == start) 392d418c81eSbellard return 0; 39354936004Sbellard 394*83fb7adfSbellard start &= qemu_host_page_mask; 395d418c81eSbellard return msync((void *)start, end - start, flags); 39654936004Sbellard } 39754936004Sbellard 398