xref: /qemu/linux-user/mmap.c (revision 83fb7adf)
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