xref: /qemu/linux-user/mmap.c (revision 35865339)
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
178167ee88SBlue Swirl  *  along with this program; if not, see <http://www.gnu.org/licenses/>.
1854936004Sbellard  */
1954936004Sbellard #include <stdlib.h>
2054936004Sbellard #include <stdio.h>
2154936004Sbellard #include <stdarg.h>
2254936004Sbellard #include <string.h>
2354936004Sbellard #include <unistd.h>
2454936004Sbellard #include <errno.h>
2554c5a2aeSedgar_igl #include <sys/types.h>
2654c5a2aeSedgar_igl #include <sys/stat.h>
2754936004Sbellard #include <sys/mman.h>
283af72a4dSblueswir1 #include <linux/mman.h>
293af72a4dSblueswir1 #include <linux/unistd.h>
3054936004Sbellard 
3154936004Sbellard #include "qemu.h"
3278f5bf1eSblueswir1 #include "qemu-common.h"
331652b974SPaolo Bonzini #include "translate-all.h"
3454936004Sbellard 
3554936004Sbellard //#define DEBUG_MMAP
3654936004Sbellard 
371e6eec8bSBlue Swirl static pthread_mutex_t mmap_mutex = PTHREAD_MUTEX_INITIALIZER;
38dfd3f85cSJuan Quintela static __thread int mmap_lock_count;
39c8a706feSpbrook 
40c8a706feSpbrook void mmap_lock(void)
41c8a706feSpbrook {
42c8a706feSpbrook     if (mmap_lock_count++ == 0) {
43c8a706feSpbrook         pthread_mutex_lock(&mmap_mutex);
44c8a706feSpbrook     }
45c8a706feSpbrook }
46c8a706feSpbrook 
47c8a706feSpbrook void mmap_unlock(void)
48c8a706feSpbrook {
49c8a706feSpbrook     if (--mmap_lock_count == 0) {
50c8a706feSpbrook         pthread_mutex_unlock(&mmap_mutex);
51c8a706feSpbrook     }
52c8a706feSpbrook }
53d5975363Spbrook 
54d5975363Spbrook /* Grab lock to make sure things are in a consistent state after fork().  */
55d5975363Spbrook void mmap_fork_start(void)
56d5975363Spbrook {
57d5975363Spbrook     if (mmap_lock_count)
58d5975363Spbrook         abort();
59d5975363Spbrook     pthread_mutex_lock(&mmap_mutex);
60d5975363Spbrook }
61d5975363Spbrook 
62d5975363Spbrook void mmap_fork_end(int child)
63d5975363Spbrook {
64d5975363Spbrook     if (child)
65d5975363Spbrook         pthread_mutex_init(&mmap_mutex, NULL);
66d5975363Spbrook     else
67d5975363Spbrook         pthread_mutex_unlock(&mmap_mutex);
68d5975363Spbrook }
69c8a706feSpbrook 
7053a5960aSpbrook /* NOTE: all the constants are the HOST ones, but addresses are target. */
71992f48a0Sblueswir1 int target_mprotect(abi_ulong start, abi_ulong len, int prot)
7254936004Sbellard {
73992f48a0Sblueswir1     abi_ulong end, host_start, host_end, addr;
7454936004Sbellard     int prot1, ret;
7554936004Sbellard 
7654936004Sbellard #ifdef DEBUG_MMAP
770bf9e31aSBlue Swirl     printf("mprotect: start=0x" TARGET_ABI_FMT_lx
780bf9e31aSBlue Swirl            "len=0x" TARGET_ABI_FMT_lx " prot=%c%c%c\n", start, len,
7954936004Sbellard            prot & PROT_READ ? 'r' : '-',
8054936004Sbellard            prot & PROT_WRITE ? 'w' : '-',
8154936004Sbellard            prot & PROT_EXEC ? 'x' : '-');
8254936004Sbellard #endif
8354936004Sbellard 
8454936004Sbellard     if ((start & ~TARGET_PAGE_MASK) != 0)
8554936004Sbellard         return -EINVAL;
8654936004Sbellard     len = TARGET_PAGE_ALIGN(len);
8754936004Sbellard     end = start + len;
8854936004Sbellard     if (end < start)
8954936004Sbellard         return -EINVAL;
90171cd1cdSbalrog     prot &= PROT_READ | PROT_WRITE | PROT_EXEC;
9154936004Sbellard     if (len == 0)
9254936004Sbellard         return 0;
9354936004Sbellard 
94c8a706feSpbrook     mmap_lock();
9583fb7adfSbellard     host_start = start & qemu_host_page_mask;
9654936004Sbellard     host_end = HOST_PAGE_ALIGN(end);
9754936004Sbellard     if (start > host_start) {
9854936004Sbellard         /* handle host page containing start */
9954936004Sbellard         prot1 = prot;
10054936004Sbellard         for(addr = host_start; addr < start; addr += TARGET_PAGE_SIZE) {
10154936004Sbellard             prot1 |= page_get_flags(addr);
10254936004Sbellard         }
10383fb7adfSbellard         if (host_end == host_start + qemu_host_page_size) {
104d418c81eSbellard             for(addr = end; addr < host_end; addr += TARGET_PAGE_SIZE) {
105d418c81eSbellard                 prot1 |= page_get_flags(addr);
106d418c81eSbellard             }
107d418c81eSbellard             end = host_end;
108d418c81eSbellard         }
10953a5960aSpbrook         ret = mprotect(g2h(host_start), qemu_host_page_size, prot1 & PAGE_BITS);
11054936004Sbellard         if (ret != 0)
111c8a706feSpbrook             goto error;
11283fb7adfSbellard         host_start += qemu_host_page_size;
11354936004Sbellard     }
11454936004Sbellard     if (end < host_end) {
11554936004Sbellard         prot1 = prot;
11654936004Sbellard         for(addr = end; addr < host_end; addr += TARGET_PAGE_SIZE) {
11754936004Sbellard             prot1 |= page_get_flags(addr);
11854936004Sbellard         }
11953a5960aSpbrook         ret = mprotect(g2h(host_end - qemu_host_page_size), qemu_host_page_size,
12054936004Sbellard                        prot1 & PAGE_BITS);
12154936004Sbellard         if (ret != 0)
122c8a706feSpbrook             goto error;
12383fb7adfSbellard         host_end -= qemu_host_page_size;
12454936004Sbellard     }
12554936004Sbellard 
12654936004Sbellard     /* handle the pages in the middle */
12754936004Sbellard     if (host_start < host_end) {
12853a5960aSpbrook         ret = mprotect(g2h(host_start), host_end - host_start, prot);
12954936004Sbellard         if (ret != 0)
130c8a706feSpbrook             goto error;
13154936004Sbellard     }
13254936004Sbellard     page_set_flags(start, start + len, prot | PAGE_VALID);
133c8a706feSpbrook     mmap_unlock();
13454936004Sbellard     return 0;
135c8a706feSpbrook error:
136c8a706feSpbrook     mmap_unlock();
137c8a706feSpbrook     return ret;
13854936004Sbellard }
13954936004Sbellard 
14054936004Sbellard /* map an incomplete host page */
141992f48a0Sblueswir1 static int mmap_frag(abi_ulong real_start,
142992f48a0Sblueswir1                      abi_ulong start, abi_ulong end,
143992f48a0Sblueswir1                      int prot, int flags, int fd, abi_ulong offset)
14454936004Sbellard {
14580210bcdSths     abi_ulong real_end, addr;
14653a5960aSpbrook     void *host_start;
14754936004Sbellard     int prot1, prot_new;
14854936004Sbellard 
14953a5960aSpbrook     real_end = real_start + qemu_host_page_size;
15053a5960aSpbrook     host_start = g2h(real_start);
15154936004Sbellard 
15254936004Sbellard     /* get the protection of the target pages outside the mapping */
15354936004Sbellard     prot1 = 0;
15453a5960aSpbrook     for(addr = real_start; addr < real_end; addr++) {
15554936004Sbellard         if (addr < start || addr >= end)
15654936004Sbellard             prot1 |= page_get_flags(addr);
15754936004Sbellard     }
15854936004Sbellard 
15954936004Sbellard     if (prot1 == 0) {
16054936004Sbellard         /* no page was there, so we allocate one */
16180210bcdSths         void *p = mmap(host_start, qemu_host_page_size, prot,
16254936004Sbellard                        flags | MAP_ANONYMOUS, -1, 0);
16380210bcdSths         if (p == MAP_FAILED)
16480210bcdSths             return -1;
16553a5960aSpbrook         prot1 = prot;
16654936004Sbellard     }
16754936004Sbellard     prot1 &= PAGE_BITS;
16854936004Sbellard 
16954936004Sbellard     prot_new = prot | prot1;
17054936004Sbellard     if (!(flags & MAP_ANONYMOUS)) {
17154936004Sbellard         /* msync() won't work here, so we return an error if write is
17254936004Sbellard            possible while it is a shared mapping */
17354936004Sbellard         if ((flags & MAP_TYPE) == MAP_SHARED &&
17454936004Sbellard             (prot & PROT_WRITE))
175ee636500SJuan Quintela             return -1;
17654936004Sbellard 
17754936004Sbellard         /* adjust protection to be able to read */
17854936004Sbellard         if (!(prot1 & PROT_WRITE))
17953a5960aSpbrook             mprotect(host_start, qemu_host_page_size, prot1 | PROT_WRITE);
18054936004Sbellard 
18154936004Sbellard         /* read the corresponding file data */
182fb7e378cSKirill A. Shutemov         if (pread(fd, g2h(start), end - start, offset) == -1)
183fb7e378cSKirill A. Shutemov             return -1;
18454936004Sbellard 
18554936004Sbellard         /* put final protection */
18654936004Sbellard         if (prot_new != (prot1 | PROT_WRITE))
18753a5960aSpbrook             mprotect(host_start, qemu_host_page_size, prot_new);
18854936004Sbellard     } else {
18954936004Sbellard         /* just update the protection */
19054936004Sbellard         if (prot_new != prot1) {
19153a5960aSpbrook             mprotect(host_start, qemu_host_page_size, prot_new);
19254936004Sbellard         }
19354936004Sbellard     }
19454936004Sbellard     return 0;
19554936004Sbellard }
19654936004Sbellard 
19714f24e14SRichard Henderson #if HOST_LONG_BITS == 64 && TARGET_ABI_BITS == 64
19814f24e14SRichard Henderson # define TASK_UNMAPPED_BASE  (1ul << 38)
19914f24e14SRichard Henderson #elif defined(__CYGWIN__)
200a03e2d42Sbellard /* Cygwin doesn't have a whole lot of address space.  */
20114f24e14SRichard Henderson # define TASK_UNMAPPED_BASE  0x18000000
202a03e2d42Sbellard #else
20314f24e14SRichard Henderson # define TASK_UNMAPPED_BASE  0x40000000
204a03e2d42Sbellard #endif
20559e9d91cSPeter Maydell abi_ulong mmap_next_start = TASK_UNMAPPED_BASE;
206a03e2d42Sbellard 
2070776590dSpbrook unsigned long last_brk;
2080776590dSpbrook 
2094e655712SPeter Maydell #ifdef CONFIG_USE_GUEST_BASE
21068a1c816SPaul Brook /* Subroutine of mmap_find_vma, used when we have pre-allocated a chunk
21168a1c816SPaul Brook    of guest address space.  */
21268a1c816SPaul Brook static abi_ulong mmap_find_vma_reserved(abi_ulong start, abi_ulong size)
21368a1c816SPaul Brook {
21468a1c816SPaul Brook     abi_ulong addr;
21559e9d91cSPeter Maydell     abi_ulong end_addr;
21668a1c816SPaul Brook     int prot;
21768a1c816SPaul Brook     int looped = 0;
21868a1c816SPaul Brook 
21918e9ea8aSAurelien Jarno     if (size > RESERVED_VA) {
22068a1c816SPaul Brook         return (abi_ulong)-1;
22168a1c816SPaul Brook     }
22268a1c816SPaul Brook 
22359e9d91cSPeter Maydell     size = HOST_PAGE_ALIGN(size);
22459e9d91cSPeter Maydell     end_addr = start + size;
22559e9d91cSPeter Maydell     if (end_addr > RESERVED_VA) {
22659e9d91cSPeter Maydell         end_addr = RESERVED_VA;
22759e9d91cSPeter Maydell     }
22859e9d91cSPeter Maydell     addr = end_addr - qemu_host_page_size;
22959e9d91cSPeter Maydell 
23059e9d91cSPeter Maydell     while (1) {
23159e9d91cSPeter Maydell         if (addr > end_addr) {
23268a1c816SPaul Brook             if (looped) {
23368a1c816SPaul Brook                 return (abi_ulong)-1;
23468a1c816SPaul Brook             }
23559e9d91cSPeter Maydell             end_addr = RESERVED_VA;
23659e9d91cSPeter Maydell             addr = end_addr - qemu_host_page_size;
23768a1c816SPaul Brook             looped = 1;
23868a1c816SPaul Brook             continue;
23968a1c816SPaul Brook         }
24068a1c816SPaul Brook         prot = page_get_flags(addr);
24168a1c816SPaul Brook         if (prot) {
24259e9d91cSPeter Maydell             end_addr = addr;
24368a1c816SPaul Brook         }
24459e9d91cSPeter Maydell         if (addr + size == end_addr) {
24559e9d91cSPeter Maydell             break;
24668a1c816SPaul Brook         }
24759e9d91cSPeter Maydell         addr -= qemu_host_page_size;
24859e9d91cSPeter Maydell     }
24959e9d91cSPeter Maydell 
25059e9d91cSPeter Maydell     if (start == mmap_next_start) {
25168a1c816SPaul Brook         mmap_next_start = addr;
25259e9d91cSPeter Maydell     }
25359e9d91cSPeter Maydell 
25459e9d91cSPeter Maydell     return addr;
25568a1c816SPaul Brook }
2564e655712SPeter Maydell #endif
25768a1c816SPaul Brook 
258fe3b4152SKirill A. Shutemov /*
259fe3b4152SKirill A. Shutemov  * Find and reserve a free memory area of size 'size'. The search
260fe3b4152SKirill A. Shutemov  * starts at 'start'.
261fe3b4152SKirill A. Shutemov  * It must be called with mmap_lock() held.
262fe3b4152SKirill A. Shutemov  * Return -1 if error.
263a03e2d42Sbellard  */
2649ad197d9SRiku Voipio abi_ulong mmap_find_vma(abi_ulong start, abi_ulong size)
265a03e2d42Sbellard {
26614f24e14SRichard Henderson     void *ptr, *prev;
267fe3b4152SKirill A. Shutemov     abi_ulong addr;
26814f24e14SRichard Henderson     int wrapped, repeat;
269fe3b4152SKirill A. Shutemov 
270fe3b4152SKirill A. Shutemov     /* If 'start' == 0, then a default start address is used. */
27114f24e14SRichard Henderson     if (start == 0) {
272fe3b4152SKirill A. Shutemov         start = mmap_next_start;
27314f24e14SRichard Henderson     } else {
27414f24e14SRichard Henderson         start &= qemu_host_page_mask;
27514f24e14SRichard Henderson     }
27614f24e14SRichard Henderson 
27714f24e14SRichard Henderson     size = HOST_PAGE_ALIGN(size);
278fe3b4152SKirill A. Shutemov 
2794e655712SPeter Maydell #ifdef CONFIG_USE_GUEST_BASE
28018e9ea8aSAurelien Jarno     if (RESERVED_VA) {
28168a1c816SPaul Brook         return mmap_find_vma_reserved(start, size);
28268a1c816SPaul Brook     }
2834e655712SPeter Maydell #endif
28468a1c816SPaul Brook 
285a03e2d42Sbellard     addr = start;
28614f24e14SRichard Henderson     wrapped = repeat = 0;
28714f24e14SRichard Henderson     prev = 0;
288fe3b4152SKirill A. Shutemov 
28914f24e14SRichard Henderson     for (;; prev = ptr) {
290fe3b4152SKirill A. Shutemov         /*
291fe3b4152SKirill A. Shutemov          * Reserve needed memory area to avoid a race.
292fe3b4152SKirill A. Shutemov          * It should be discarded using:
293fe3b4152SKirill A. Shutemov          *  - mmap() with MAP_FIXED flag
294fe3b4152SKirill A. Shutemov          *  - mremap() with MREMAP_FIXED flag
295fe3b4152SKirill A. Shutemov          *  - shmat() with SHM_REMAP flag
296fe3b4152SKirill A. Shutemov          */
29714f24e14SRichard Henderson         ptr = mmap(g2h(addr), size, PROT_NONE,
298fe3b4152SKirill A. Shutemov                    MAP_ANONYMOUS|MAP_PRIVATE|MAP_NORESERVE, -1, 0);
299fe3b4152SKirill A. Shutemov 
300fe3b4152SKirill A. Shutemov         /* ENOMEM, if host address space has no memory */
30114f24e14SRichard Henderson         if (ptr == MAP_FAILED) {
302a03e2d42Sbellard             return (abi_ulong)-1;
303a03e2d42Sbellard         }
304fe3b4152SKirill A. Shutemov 
30514f24e14SRichard Henderson         /* Count the number of sequential returns of the same address.
30614f24e14SRichard Henderson            This is used to modify the search algorithm below.  */
30714f24e14SRichard Henderson         repeat = (ptr == prev ? repeat + 1 : 0);
308fe3b4152SKirill A. Shutemov 
30914f24e14SRichard Henderson         if (h2g_valid(ptr + size - 1)) {
31014f24e14SRichard Henderson             addr = h2g(ptr);
31114f24e14SRichard Henderson 
31214f24e14SRichard Henderson             if ((addr & ~TARGET_PAGE_MASK) == 0) {
31314f24e14SRichard Henderson                 /* Success.  */
31414f24e14SRichard Henderson                 if (start == mmap_next_start && addr >= TASK_UNMAPPED_BASE) {
31514f24e14SRichard Henderson                     mmap_next_start = addr + size;
31614f24e14SRichard Henderson                 }
31714f24e14SRichard Henderson                 return addr;
31814f24e14SRichard Henderson             }
31914f24e14SRichard Henderson 
32014f24e14SRichard Henderson             /* The address is not properly aligned for the target.  */
32114f24e14SRichard Henderson             switch (repeat) {
32214f24e14SRichard Henderson             case 0:
32314f24e14SRichard Henderson                 /* Assume the result that the kernel gave us is the
32414f24e14SRichard Henderson                    first with enough free space, so start again at the
32514f24e14SRichard Henderson                    next higher target page.  */
32614f24e14SRichard Henderson                 addr = TARGET_PAGE_ALIGN(addr);
32714f24e14SRichard Henderson                 break;
32814f24e14SRichard Henderson             case 1:
32914f24e14SRichard Henderson                 /* Sometimes the kernel decides to perform the allocation
33014f24e14SRichard Henderson                    at the top end of memory instead.  */
33114f24e14SRichard Henderson                 addr &= TARGET_PAGE_MASK;
33214f24e14SRichard Henderson                 break;
33314f24e14SRichard Henderson             case 2:
33414f24e14SRichard Henderson                 /* Start over at low memory.  */
33514f24e14SRichard Henderson                 addr = 0;
33614f24e14SRichard Henderson                 break;
33714f24e14SRichard Henderson             default:
33814f24e14SRichard Henderson                 /* Fail.  This unaligned block must the last.  */
33914f24e14SRichard Henderson                 addr = -1;
34014f24e14SRichard Henderson                 break;
34114f24e14SRichard Henderson             }
34214f24e14SRichard Henderson         } else {
34314f24e14SRichard Henderson             /* Since the result the kernel gave didn't fit, start
34414f24e14SRichard Henderson                again at low memory.  If any repetition, fail.  */
34514f24e14SRichard Henderson             addr = (repeat ? -1 : 0);
34614f24e14SRichard Henderson         }
34714f24e14SRichard Henderson 
34814f24e14SRichard Henderson         /* Unmap and try again.  */
34914f24e14SRichard Henderson         munmap(ptr, size);
35014f24e14SRichard Henderson 
35114f24e14SRichard Henderson         /* ENOMEM if we checked the whole of the target address space.  */
352d0b3e4f5SBlue Swirl         if (addr == (abi_ulong)-1) {
35314f24e14SRichard Henderson             return (abi_ulong)-1;
35414f24e14SRichard Henderson         } else if (addr == 0) {
35514f24e14SRichard Henderson             if (wrapped) {
35614f24e14SRichard Henderson                 return (abi_ulong)-1;
35714f24e14SRichard Henderson             }
35814f24e14SRichard Henderson             wrapped = 1;
35914f24e14SRichard Henderson             /* Don't actually use 0 when wrapping, instead indicate
3608186e783SStefan Weil                that we'd truly like an allocation in low memory.  */
36114f24e14SRichard Henderson             addr = (mmap_min_addr > TARGET_PAGE_SIZE
36214f24e14SRichard Henderson                      ? TARGET_PAGE_ALIGN(mmap_min_addr)
36314f24e14SRichard Henderson                      : TARGET_PAGE_SIZE);
36414f24e14SRichard Henderson         } else if (wrapped && addr >= start) {
36514f24e14SRichard Henderson             return (abi_ulong)-1;
36614f24e14SRichard Henderson         }
36714f24e14SRichard Henderson     }
368a03e2d42Sbellard }
369a03e2d42Sbellard 
37054936004Sbellard /* NOTE: all the constants are the HOST ones */
371992f48a0Sblueswir1 abi_long target_mmap(abi_ulong start, abi_ulong len, int prot,
372992f48a0Sblueswir1                      int flags, int fd, abi_ulong offset)
37354936004Sbellard {
374992f48a0Sblueswir1     abi_ulong ret, end, real_start, real_end, retaddr, host_offset, host_len;
37554936004Sbellard 
376c8a706feSpbrook     mmap_lock();
37754936004Sbellard #ifdef DEBUG_MMAP
37854936004Sbellard     {
3790bf9e31aSBlue Swirl         printf("mmap: start=0x" TARGET_ABI_FMT_lx
3800bf9e31aSBlue Swirl                " len=0x" TARGET_ABI_FMT_lx " prot=%c%c%c flags=",
38154936004Sbellard                start, len,
38254936004Sbellard                prot & PROT_READ ? 'r' : '-',
38354936004Sbellard                prot & PROT_WRITE ? 'w' : '-',
38454936004Sbellard                prot & PROT_EXEC ? 'x' : '-');
38554936004Sbellard         if (flags & MAP_FIXED)
38654936004Sbellard             printf("MAP_FIXED ");
38754936004Sbellard         if (flags & MAP_ANONYMOUS)
38854936004Sbellard             printf("MAP_ANON ");
38954936004Sbellard         switch(flags & MAP_TYPE) {
39054936004Sbellard         case MAP_PRIVATE:
39154936004Sbellard             printf("MAP_PRIVATE ");
39254936004Sbellard             break;
39354936004Sbellard         case MAP_SHARED:
39454936004Sbellard             printf("MAP_SHARED ");
39554936004Sbellard             break;
39654936004Sbellard         default:
39754936004Sbellard             printf("[MAP_TYPE=0x%x] ", flags & MAP_TYPE);
39854936004Sbellard             break;
39954936004Sbellard         }
4000bf9e31aSBlue Swirl         printf("fd=%d offset=" TARGET_ABI_FMT_lx "\n", fd, offset);
40154936004Sbellard     }
40254936004Sbellard #endif
40354936004Sbellard 
404e89f07d3Spbrook     if (offset & ~TARGET_PAGE_MASK) {
405e89f07d3Spbrook         errno = EINVAL;
406c8a706feSpbrook         goto fail;
407e89f07d3Spbrook     }
40854936004Sbellard 
40954936004Sbellard     len = TARGET_PAGE_ALIGN(len);
41054936004Sbellard     if (len == 0)
411c8a706feSpbrook         goto the_end;
41253a5960aSpbrook     real_start = start & qemu_host_page_mask;
413a5e7ee46SRichard Henderson     host_offset = offset & qemu_host_page_mask;
414a5e7ee46SRichard Henderson 
415a5e7ee46SRichard Henderson     /* If the user is asking for the kernel to find a location, do that
416a5e7ee46SRichard Henderson        before we truncate the length for mapping files below.  */
417a5e7ee46SRichard Henderson     if (!(flags & MAP_FIXED)) {
418a5e7ee46SRichard Henderson         host_len = len + offset - host_offset;
419a5e7ee46SRichard Henderson         host_len = HOST_PAGE_ALIGN(host_len);
420a5e7ee46SRichard Henderson         start = mmap_find_vma(real_start, host_len);
421a5e7ee46SRichard Henderson         if (start == (abi_ulong)-1) {
422a5e7ee46SRichard Henderson             errno = ENOMEM;
423a5e7ee46SRichard Henderson             goto fail;
424a5e7ee46SRichard Henderson         }
425a5e7ee46SRichard Henderson     }
42654936004Sbellard 
42754c5a2aeSedgar_igl     /* When mapping files into a memory area larger than the file, accesses
42854c5a2aeSedgar_igl        to pages beyond the file size will cause a SIGBUS.
42954c5a2aeSedgar_igl 
43054c5a2aeSedgar_igl        For example, if mmaping a file of 100 bytes on a host with 4K pages
43154c5a2aeSedgar_igl        emulating a target with 8K pages, the target expects to be able to
43254c5a2aeSedgar_igl        access the first 8K. But the host will trap us on any access beyond
43354c5a2aeSedgar_igl        4K.
43454c5a2aeSedgar_igl 
43554c5a2aeSedgar_igl        When emulating a target with a larger page-size than the hosts, we
43654c5a2aeSedgar_igl        may need to truncate file maps at EOF and add extra anonymous pages
43754c5a2aeSedgar_igl        up to the targets page boundary.  */
43854c5a2aeSedgar_igl 
43954c5a2aeSedgar_igl     if ((qemu_real_host_page_size < TARGET_PAGE_SIZE)
44054c5a2aeSedgar_igl         && !(flags & MAP_ANONYMOUS)) {
44154c5a2aeSedgar_igl        struct stat sb;
44254c5a2aeSedgar_igl 
44354c5a2aeSedgar_igl        if (fstat (fd, &sb) == -1)
44454c5a2aeSedgar_igl            goto fail;
44554c5a2aeSedgar_igl 
44654c5a2aeSedgar_igl        /* Are we trying to create a map beyond EOF?.  */
44754c5a2aeSedgar_igl        if (offset + len > sb.st_size) {
44854c5a2aeSedgar_igl            /* If so, truncate the file map at eof aligned with
44954c5a2aeSedgar_igl               the hosts real pagesize. Additional anonymous maps
45054c5a2aeSedgar_igl               will be created beyond EOF.  */
45154c5a2aeSedgar_igl            len = (sb.st_size - offset);
45254c5a2aeSedgar_igl            len += qemu_real_host_page_size - 1;
45354c5a2aeSedgar_igl            len &= ~(qemu_real_host_page_size - 1);
45454c5a2aeSedgar_igl        }
45554c5a2aeSedgar_igl     }
45654c5a2aeSedgar_igl 
45754936004Sbellard     if (!(flags & MAP_FIXED)) {
458a5e7ee46SRichard Henderson         unsigned long host_start;
459a03e2d42Sbellard         void *p;
460a5e7ee46SRichard Henderson 
46154936004Sbellard         host_len = len + offset - host_offset;
462a03e2d42Sbellard         host_len = HOST_PAGE_ALIGN(host_len);
463a5e7ee46SRichard Henderson 
464a03e2d42Sbellard         /* Note: we prefer to control the mapping address. It is
465a03e2d42Sbellard            especially important if qemu_host_page_size >
466a03e2d42Sbellard            qemu_real_host_page_size */
467a5e7ee46SRichard Henderson         p = mmap(g2h(start), host_len, prot,
468a5e7ee46SRichard Henderson                  flags | MAP_FIXED | MAP_ANONYMOUS, -1, 0);
46980210bcdSths         if (p == MAP_FAILED)
470c8a706feSpbrook             goto fail;
47154936004Sbellard         /* update start so that it points to the file position at 'offset' */
47280210bcdSths         host_start = (unsigned long)p;
47354c5a2aeSedgar_igl         if (!(flags & MAP_ANONYMOUS)) {
474a5e7ee46SRichard Henderson             p = mmap(g2h(start), len, prot,
47554c5a2aeSedgar_igl                      flags | MAP_FIXED, fd, host_offset);
4768384274eSJürg Billeter             if (p == MAP_FAILED) {
4778384274eSJürg Billeter                 munmap(g2h(start), host_len);
4788384274eSJürg Billeter                 goto fail;
4798384274eSJürg Billeter             }
48053a5960aSpbrook             host_start += offset - host_offset;
48154c5a2aeSedgar_igl         }
48253a5960aSpbrook         start = h2g(host_start);
483a03e2d42Sbellard     } else {
484e89f07d3Spbrook         if (start & ~TARGET_PAGE_MASK) {
485e89f07d3Spbrook             errno = EINVAL;
486c8a706feSpbrook             goto fail;
487e89f07d3Spbrook         }
48854936004Sbellard         end = start + len;
48953a5960aSpbrook         real_end = HOST_PAGE_ALIGN(end);
49054936004Sbellard 
49145bc1f52Saurel32 	/*
49245bc1f52Saurel32 	 * Test if requested memory area fits target address space
49345bc1f52Saurel32 	 * It can fail only on 64-bit host with 32-bit target.
49445bc1f52Saurel32 	 * On any other target/host host mmap() handles this error correctly.
49545bc1f52Saurel32 	 */
49645bc1f52Saurel32         if ((unsigned long)start + len - 1 > (abi_ulong) -1) {
49745bc1f52Saurel32             errno = EINVAL;
49845bc1f52Saurel32             goto fail;
49945bc1f52Saurel32         }
50045bc1f52Saurel32 
50154936004Sbellard         /* worst case: we cannot map the file because the offset is not
50254936004Sbellard            aligned, so we read it */
50354936004Sbellard         if (!(flags & MAP_ANONYMOUS) &&
50483fb7adfSbellard             (offset & ~qemu_host_page_mask) != (start & ~qemu_host_page_mask)) {
50554936004Sbellard             /* msync() won't work here, so we return an error if write is
50654936004Sbellard                possible while it is a shared mapping */
50754936004Sbellard             if ((flags & MAP_TYPE) == MAP_SHARED &&
508e89f07d3Spbrook                 (prot & PROT_WRITE)) {
509e89f07d3Spbrook                 errno = EINVAL;
510c8a706feSpbrook                 goto fail;
511e89f07d3Spbrook             }
51254936004Sbellard             retaddr = target_mmap(start, len, prot | PROT_WRITE,
51354936004Sbellard                                   MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS,
51454936004Sbellard                                   -1, 0);
51554936004Sbellard             if (retaddr == -1)
516c8a706feSpbrook                 goto fail;
517fb7e378cSKirill A. Shutemov             if (pread(fd, g2h(start), len, offset) == -1)
518fb7e378cSKirill A. Shutemov                 goto fail;
51954936004Sbellard             if (!(prot & PROT_WRITE)) {
52054936004Sbellard                 ret = target_mprotect(start, len, prot);
521c8a706feSpbrook                 if (ret != 0) {
522c8a706feSpbrook                     start = ret;
523c8a706feSpbrook                     goto the_end;
524c8a706feSpbrook                 }
52554936004Sbellard             }
52654936004Sbellard             goto the_end;
52754936004Sbellard         }
52854936004Sbellard 
52954936004Sbellard         /* handle the start of the mapping */
53053a5960aSpbrook         if (start > real_start) {
53153a5960aSpbrook             if (real_end == real_start + qemu_host_page_size) {
53254936004Sbellard                 /* one single host page */
53353a5960aSpbrook                 ret = mmap_frag(real_start, start, end,
53454936004Sbellard                                 prot, flags, fd, offset);
53554936004Sbellard                 if (ret == -1)
536c8a706feSpbrook                     goto fail;
53754936004Sbellard                 goto the_end1;
53854936004Sbellard             }
53953a5960aSpbrook             ret = mmap_frag(real_start, start, real_start + qemu_host_page_size,
54054936004Sbellard                             prot, flags, fd, offset);
54154936004Sbellard             if (ret == -1)
542c8a706feSpbrook                 goto fail;
54353a5960aSpbrook             real_start += qemu_host_page_size;
54454936004Sbellard         }
54554936004Sbellard         /* handle the end of the mapping */
54653a5960aSpbrook         if (end < real_end) {
54753a5960aSpbrook             ret = mmap_frag(real_end - qemu_host_page_size,
54853a5960aSpbrook                             real_end - qemu_host_page_size, real_end,
54954936004Sbellard                             prot, flags, fd,
55053a5960aSpbrook                             offset + real_end - qemu_host_page_size - start);
55154936004Sbellard             if (ret == -1)
552c8a706feSpbrook                 goto fail;
55353a5960aSpbrook             real_end -= qemu_host_page_size;
55454936004Sbellard         }
55554936004Sbellard 
55654936004Sbellard         /* map the middle (easier) */
55753a5960aSpbrook         if (real_start < real_end) {
55880210bcdSths             void *p;
5594a585ccbSbellard             unsigned long offset1;
5604a585ccbSbellard             if (flags & MAP_ANONYMOUS)
5614a585ccbSbellard                 offset1 = 0;
5624a585ccbSbellard             else
56353a5960aSpbrook                 offset1 = offset + real_start - start;
56480210bcdSths             p = mmap(g2h(real_start), real_end - real_start,
5654a585ccbSbellard                      prot, flags, fd, offset1);
56680210bcdSths             if (p == MAP_FAILED)
567c8a706feSpbrook                 goto fail;
56854936004Sbellard         }
569a03e2d42Sbellard     }
57054936004Sbellard  the_end1:
57154936004Sbellard     page_set_flags(start, start + len, prot | PAGE_VALID);
57254936004Sbellard  the_end:
57354936004Sbellard #ifdef DEBUG_MMAP
5740bf9e31aSBlue Swirl     printf("ret=0x" TARGET_ABI_FMT_lx "\n", start);
57554936004Sbellard     page_dump(stdout);
57654936004Sbellard     printf("\n");
57754936004Sbellard #endif
578*35865339SPaolo Bonzini     tb_invalidate_phys_range(start, start + len);
579c8a706feSpbrook     mmap_unlock();
58054936004Sbellard     return start;
581c8a706feSpbrook fail:
582c8a706feSpbrook     mmap_unlock();
583c8a706feSpbrook     return -1;
58454936004Sbellard }
58554936004Sbellard 
58668a1c816SPaul Brook static void mmap_reserve(abi_ulong start, abi_ulong size)
58768a1c816SPaul Brook {
58868a1c816SPaul Brook     abi_ulong real_start;
58968a1c816SPaul Brook     abi_ulong real_end;
59068a1c816SPaul Brook     abi_ulong addr;
59168a1c816SPaul Brook     abi_ulong end;
59268a1c816SPaul Brook     int prot;
59368a1c816SPaul Brook 
59468a1c816SPaul Brook     real_start = start & qemu_host_page_mask;
59568a1c816SPaul Brook     real_end = HOST_PAGE_ALIGN(start + size);
59668a1c816SPaul Brook     end = start + size;
59768a1c816SPaul Brook     if (start > real_start) {
59868a1c816SPaul Brook         /* handle host page containing start */
59968a1c816SPaul Brook         prot = 0;
60068a1c816SPaul Brook         for (addr = real_start; addr < start; addr += TARGET_PAGE_SIZE) {
60168a1c816SPaul Brook             prot |= page_get_flags(addr);
60268a1c816SPaul Brook         }
60368a1c816SPaul Brook         if (real_end == real_start + qemu_host_page_size) {
60468a1c816SPaul Brook             for (addr = end; addr < real_end; addr += TARGET_PAGE_SIZE) {
60568a1c816SPaul Brook                 prot |= page_get_flags(addr);
60668a1c816SPaul Brook             }
60768a1c816SPaul Brook             end = real_end;
60868a1c816SPaul Brook         }
60968a1c816SPaul Brook         if (prot != 0)
61068a1c816SPaul Brook             real_start += qemu_host_page_size;
61168a1c816SPaul Brook     }
61268a1c816SPaul Brook     if (end < real_end) {
61368a1c816SPaul Brook         prot = 0;
61468a1c816SPaul Brook         for (addr = end; addr < real_end; addr += TARGET_PAGE_SIZE) {
61568a1c816SPaul Brook             prot |= page_get_flags(addr);
61668a1c816SPaul Brook         }
61768a1c816SPaul Brook         if (prot != 0)
61868a1c816SPaul Brook             real_end -= qemu_host_page_size;
61968a1c816SPaul Brook     }
62068a1c816SPaul Brook     if (real_start != real_end) {
62168a1c816SPaul Brook         mmap(g2h(real_start), real_end - real_start, PROT_NONE,
62268a1c816SPaul Brook                  MAP_FIXED | MAP_ANONYMOUS | MAP_PRIVATE | MAP_NORESERVE,
62368a1c816SPaul Brook                  -1, 0);
62468a1c816SPaul Brook     }
62568a1c816SPaul Brook }
62668a1c816SPaul Brook 
627992f48a0Sblueswir1 int target_munmap(abi_ulong start, abi_ulong len)
62854936004Sbellard {
629992f48a0Sblueswir1     abi_ulong end, real_start, real_end, addr;
63054936004Sbellard     int prot, ret;
63154936004Sbellard 
63254936004Sbellard #ifdef DEBUG_MMAP
6330bf9e31aSBlue Swirl     printf("munmap: start=0x" TARGET_ABI_FMT_lx " len=0x"
6340bf9e31aSBlue Swirl            TARGET_ABI_FMT_lx "\n",
6350bf9e31aSBlue Swirl            start, len);
63654936004Sbellard #endif
63754936004Sbellard     if (start & ~TARGET_PAGE_MASK)
63854936004Sbellard         return -EINVAL;
63954936004Sbellard     len = TARGET_PAGE_ALIGN(len);
64054936004Sbellard     if (len == 0)
64154936004Sbellard         return -EINVAL;
642c8a706feSpbrook     mmap_lock();
64354936004Sbellard     end = start + len;
64453a5960aSpbrook     real_start = start & qemu_host_page_mask;
64553a5960aSpbrook     real_end = HOST_PAGE_ALIGN(end);
64654936004Sbellard 
64753a5960aSpbrook     if (start > real_start) {
64854936004Sbellard         /* handle host page containing start */
64954936004Sbellard         prot = 0;
65053a5960aSpbrook         for(addr = real_start; addr < start; addr += TARGET_PAGE_SIZE) {
65154936004Sbellard             prot |= page_get_flags(addr);
65254936004Sbellard         }
65353a5960aSpbrook         if (real_end == real_start + qemu_host_page_size) {
65453a5960aSpbrook             for(addr = end; addr < real_end; addr += TARGET_PAGE_SIZE) {
655d418c81eSbellard                 prot |= page_get_flags(addr);
656d418c81eSbellard             }
65753a5960aSpbrook             end = real_end;
658d418c81eSbellard         }
65954936004Sbellard         if (prot != 0)
66053a5960aSpbrook             real_start += qemu_host_page_size;
66154936004Sbellard     }
66253a5960aSpbrook     if (end < real_end) {
66354936004Sbellard         prot = 0;
66453a5960aSpbrook         for(addr = end; addr < real_end; addr += TARGET_PAGE_SIZE) {
66554936004Sbellard             prot |= page_get_flags(addr);
66654936004Sbellard         }
66754936004Sbellard         if (prot != 0)
66853a5960aSpbrook             real_end -= qemu_host_page_size;
66954936004Sbellard     }
67054936004Sbellard 
671c8a706feSpbrook     ret = 0;
67254936004Sbellard     /* unmap what we can */
67353a5960aSpbrook     if (real_start < real_end) {
67418e9ea8aSAurelien Jarno         if (RESERVED_VA) {
67568a1c816SPaul Brook             mmap_reserve(real_start, real_end - real_start);
67668a1c816SPaul Brook         } else {
6774118a970Sj_mayer             ret = munmap(g2h(real_start), real_end - real_start);
67854936004Sbellard         }
67968a1c816SPaul Brook     }
68054936004Sbellard 
68177a8f1a5SAlexander Graf     if (ret == 0) {
68254936004Sbellard         page_set_flags(start, start + len, 0);
683*35865339SPaolo Bonzini         tb_invalidate_phys_range(start, start + len);
68477a8f1a5SAlexander Graf     }
685c8a706feSpbrook     mmap_unlock();
686c8a706feSpbrook     return ret;
68754936004Sbellard }
68854936004Sbellard 
689992f48a0Sblueswir1 abi_long target_mremap(abi_ulong old_addr, abi_ulong old_size,
690992f48a0Sblueswir1                        abi_ulong new_size, unsigned long flags,
691992f48a0Sblueswir1                        abi_ulong new_addr)
69254936004Sbellard {
69354936004Sbellard     int prot;
694f19412a2Saurel32     void *host_addr;
69554936004Sbellard 
696c8a706feSpbrook     mmap_lock();
697f19412a2Saurel32 
69868a1c816SPaul Brook     if (flags & MREMAP_FIXED) {
6993af72a4dSblueswir1         host_addr = (void *) syscall(__NR_mremap, g2h(old_addr),
7003af72a4dSblueswir1                                      old_size, new_size,
7013af72a4dSblueswir1                                      flags,
70268a1c816SPaul Brook                                      g2h(new_addr));
70368a1c816SPaul Brook 
70418e9ea8aSAurelien Jarno         if (RESERVED_VA && host_addr != MAP_FAILED) {
70568a1c816SPaul Brook             /* If new and old addresses overlap then the above mremap will
70668a1c816SPaul Brook                already have failed with EINVAL.  */
70768a1c816SPaul Brook             mmap_reserve(old_addr, old_size);
70868a1c816SPaul Brook         }
70968a1c816SPaul Brook     } else if (flags & MREMAP_MAYMOVE) {
710f19412a2Saurel32         abi_ulong mmap_start;
711f19412a2Saurel32 
712f19412a2Saurel32         mmap_start = mmap_find_vma(0, new_size);
713f19412a2Saurel32 
714f19412a2Saurel32         if (mmap_start == -1) {
715f19412a2Saurel32             errno = ENOMEM;
716f19412a2Saurel32             host_addr = MAP_FAILED;
71768a1c816SPaul Brook         } else {
7183af72a4dSblueswir1             host_addr = (void *) syscall(__NR_mremap, g2h(old_addr),
7193af72a4dSblueswir1                                          old_size, new_size,
7203af72a4dSblueswir1                                          flags | MREMAP_FIXED,
7213af72a4dSblueswir1                                          g2h(mmap_start));
722c65ffe6dSamateur             if ( RESERVED_VA ) {
72368a1c816SPaul Brook                 mmap_reserve(old_addr, old_size);
72468a1c816SPaul Brook             }
725c65ffe6dSamateur         }
7263af72a4dSblueswir1     } else {
72768a1c816SPaul Brook         int prot = 0;
72818e9ea8aSAurelien Jarno         if (RESERVED_VA && old_size < new_size) {
72968a1c816SPaul Brook             abi_ulong addr;
73068a1c816SPaul Brook             for (addr = old_addr + old_size;
73168a1c816SPaul Brook                  addr < old_addr + new_size;
73268a1c816SPaul Brook                  addr++) {
73368a1c816SPaul Brook                 prot |= page_get_flags(addr);
73468a1c816SPaul Brook             }
73568a1c816SPaul Brook         }
73668a1c816SPaul Brook         if (prot == 0) {
737f19412a2Saurel32             host_addr = mremap(g2h(old_addr), old_size, new_size, flags);
73818e9ea8aSAurelien Jarno             if (host_addr != MAP_FAILED && RESERVED_VA && old_size > new_size) {
73968a1c816SPaul Brook                 mmap_reserve(old_addr + old_size, new_size - old_size);
74068a1c816SPaul Brook             }
74168a1c816SPaul Brook         } else {
74268a1c816SPaul Brook             errno = ENOMEM;
74368a1c816SPaul Brook             host_addr = MAP_FAILED;
74468a1c816SPaul Brook         }
745f19412a2Saurel32         /* Check if address fits target address space */
746f19412a2Saurel32         if ((unsigned long)host_addr + new_size > (abi_ulong)-1) {
747f19412a2Saurel32             /* Revert mremap() changes */
748f19412a2Saurel32             host_addr = mremap(g2h(old_addr), new_size, old_size, flags);
749f19412a2Saurel32             errno = ENOMEM;
750f19412a2Saurel32             host_addr = MAP_FAILED;
751f19412a2Saurel32         }
752f19412a2Saurel32     }
753f19412a2Saurel32 
754f19412a2Saurel32     if (host_addr == MAP_FAILED) {
755c8a706feSpbrook         new_addr = -1;
756c8a706feSpbrook     } else {
757a5b85f79Sths         new_addr = h2g(host_addr);
75854936004Sbellard         prot = page_get_flags(old_addr);
75954936004Sbellard         page_set_flags(old_addr, old_addr + old_size, 0);
76054936004Sbellard         page_set_flags(new_addr, new_addr + new_size, prot | PAGE_VALID);
761c8a706feSpbrook     }
762*35865339SPaolo Bonzini     tb_invalidate_phys_range(new_addr, new_addr + new_size);
763c8a706feSpbrook     mmap_unlock();
76454936004Sbellard     return new_addr;
76554936004Sbellard }
76654936004Sbellard 
767992f48a0Sblueswir1 int target_msync(abi_ulong start, abi_ulong len, int flags)
76854936004Sbellard {
769992f48a0Sblueswir1     abi_ulong end;
77054936004Sbellard 
77154936004Sbellard     if (start & ~TARGET_PAGE_MASK)
77254936004Sbellard         return -EINVAL;
77354936004Sbellard     len = TARGET_PAGE_ALIGN(len);
77454936004Sbellard     end = start + len;
775d418c81eSbellard     if (end < start)
776d418c81eSbellard         return -EINVAL;
777d418c81eSbellard     if (end == start)
778d418c81eSbellard         return 0;
77954936004Sbellard 
78083fb7adfSbellard     start &= qemu_host_page_mask;
78153a5960aSpbrook     return msync(g2h(start), end - start, flags);
78254936004Sbellard }
783