xref: /qemu/linux-user/mmap.c (revision 35f2fd04)
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  */
19d39594e9SPeter Maydell #include "qemu/osdep.h"
2054936004Sbellard 
2154936004Sbellard #include "qemu.h"
2278f5bf1eSblueswir1 #include "qemu-common.h"
231652b974SPaolo Bonzini #include "translate-all.h"
2454936004Sbellard 
2554936004Sbellard //#define DEBUG_MMAP
2654936004Sbellard 
271e6eec8bSBlue Swirl static pthread_mutex_t mmap_mutex = PTHREAD_MUTEX_INITIALIZER;
28dfd3f85cSJuan Quintela static __thread int mmap_lock_count;
29c8a706feSpbrook 
30c8a706feSpbrook void mmap_lock(void)
31c8a706feSpbrook {
32c8a706feSpbrook     if (mmap_lock_count++ == 0) {
33c8a706feSpbrook         pthread_mutex_lock(&mmap_mutex);
34c8a706feSpbrook     }
35c8a706feSpbrook }
36c8a706feSpbrook 
37c8a706feSpbrook void mmap_unlock(void)
38c8a706feSpbrook {
39c8a706feSpbrook     if (--mmap_lock_count == 0) {
40c8a706feSpbrook         pthread_mutex_unlock(&mmap_mutex);
41c8a706feSpbrook     }
42c8a706feSpbrook }
43d5975363Spbrook 
44301e40edSAlex Bennée bool have_mmap_lock(void)
45301e40edSAlex Bennée {
46301e40edSAlex Bennée     return mmap_lock_count > 0 ? true : false;
47301e40edSAlex Bennée }
48301e40edSAlex Bennée 
49d5975363Spbrook /* Grab lock to make sure things are in a consistent state after fork().  */
50d5975363Spbrook void mmap_fork_start(void)
51d5975363Spbrook {
52d5975363Spbrook     if (mmap_lock_count)
53d5975363Spbrook         abort();
54d5975363Spbrook     pthread_mutex_lock(&mmap_mutex);
55d5975363Spbrook }
56d5975363Spbrook 
57d5975363Spbrook void mmap_fork_end(int child)
58d5975363Spbrook {
59d5975363Spbrook     if (child)
60d5975363Spbrook         pthread_mutex_init(&mmap_mutex, NULL);
61d5975363Spbrook     else
62d5975363Spbrook         pthread_mutex_unlock(&mmap_mutex);
63d5975363Spbrook }
64c8a706feSpbrook 
6553a5960aSpbrook /* NOTE: all the constants are the HOST ones, but addresses are target. */
66992f48a0Sblueswir1 int target_mprotect(abi_ulong start, abi_ulong len, int prot)
6754936004Sbellard {
68992f48a0Sblueswir1     abi_ulong end, host_start, host_end, addr;
6954936004Sbellard     int prot1, ret;
7054936004Sbellard 
7154936004Sbellard #ifdef DEBUG_MMAP
720bf9e31aSBlue Swirl     printf("mprotect: start=0x" TARGET_ABI_FMT_lx
730bf9e31aSBlue Swirl            "len=0x" TARGET_ABI_FMT_lx " prot=%c%c%c\n", start, len,
7454936004Sbellard            prot & PROT_READ ? 'r' : '-',
7554936004Sbellard            prot & PROT_WRITE ? 'w' : '-',
7654936004Sbellard            prot & PROT_EXEC ? 'x' : '-');
7754936004Sbellard #endif
7854936004Sbellard 
7954936004Sbellard     if ((start & ~TARGET_PAGE_MASK) != 0)
8054936004Sbellard         return -EINVAL;
8154936004Sbellard     len = TARGET_PAGE_ALIGN(len);
8254936004Sbellard     end = start + len;
8354936004Sbellard     if (end < start)
8454936004Sbellard         return -EINVAL;
85171cd1cdSbalrog     prot &= PROT_READ | PROT_WRITE | PROT_EXEC;
8654936004Sbellard     if (len == 0)
8754936004Sbellard         return 0;
8854936004Sbellard 
89c8a706feSpbrook     mmap_lock();
9083fb7adfSbellard     host_start = start & qemu_host_page_mask;
9154936004Sbellard     host_end = HOST_PAGE_ALIGN(end);
9254936004Sbellard     if (start > host_start) {
9354936004Sbellard         /* handle host page containing start */
9454936004Sbellard         prot1 = prot;
9554936004Sbellard         for(addr = host_start; addr < start; addr += TARGET_PAGE_SIZE) {
9654936004Sbellard             prot1 |= page_get_flags(addr);
9754936004Sbellard         }
9883fb7adfSbellard         if (host_end == host_start + qemu_host_page_size) {
99d418c81eSbellard             for(addr = end; addr < host_end; addr += TARGET_PAGE_SIZE) {
100d418c81eSbellard                 prot1 |= page_get_flags(addr);
101d418c81eSbellard             }
102d418c81eSbellard             end = host_end;
103d418c81eSbellard         }
10453a5960aSpbrook         ret = mprotect(g2h(host_start), qemu_host_page_size, prot1 & PAGE_BITS);
10554936004Sbellard         if (ret != 0)
106c8a706feSpbrook             goto error;
10783fb7adfSbellard         host_start += qemu_host_page_size;
10854936004Sbellard     }
10954936004Sbellard     if (end < host_end) {
11054936004Sbellard         prot1 = prot;
11154936004Sbellard         for(addr = end; addr < host_end; addr += TARGET_PAGE_SIZE) {
11254936004Sbellard             prot1 |= page_get_flags(addr);
11354936004Sbellard         }
11453a5960aSpbrook         ret = mprotect(g2h(host_end - qemu_host_page_size), qemu_host_page_size,
11554936004Sbellard                        prot1 & PAGE_BITS);
11654936004Sbellard         if (ret != 0)
117c8a706feSpbrook             goto error;
11883fb7adfSbellard         host_end -= qemu_host_page_size;
11954936004Sbellard     }
12054936004Sbellard 
12154936004Sbellard     /* handle the pages in the middle */
12254936004Sbellard     if (host_start < host_end) {
12353a5960aSpbrook         ret = mprotect(g2h(host_start), host_end - host_start, prot);
12454936004Sbellard         if (ret != 0)
125c8a706feSpbrook             goto error;
12654936004Sbellard     }
12754936004Sbellard     page_set_flags(start, start + len, prot | PAGE_VALID);
128c8a706feSpbrook     mmap_unlock();
12954936004Sbellard     return 0;
130c8a706feSpbrook error:
131c8a706feSpbrook     mmap_unlock();
132c8a706feSpbrook     return ret;
13354936004Sbellard }
13454936004Sbellard 
13554936004Sbellard /* map an incomplete host page */
136992f48a0Sblueswir1 static int mmap_frag(abi_ulong real_start,
137992f48a0Sblueswir1                      abi_ulong start, abi_ulong end,
138992f48a0Sblueswir1                      int prot, int flags, int fd, abi_ulong offset)
13954936004Sbellard {
14080210bcdSths     abi_ulong real_end, addr;
14153a5960aSpbrook     void *host_start;
14254936004Sbellard     int prot1, prot_new;
14354936004Sbellard 
14453a5960aSpbrook     real_end = real_start + qemu_host_page_size;
14553a5960aSpbrook     host_start = g2h(real_start);
14654936004Sbellard 
14754936004Sbellard     /* get the protection of the target pages outside the mapping */
14854936004Sbellard     prot1 = 0;
14953a5960aSpbrook     for(addr = real_start; addr < real_end; addr++) {
15054936004Sbellard         if (addr < start || addr >= end)
15154936004Sbellard             prot1 |= page_get_flags(addr);
15254936004Sbellard     }
15354936004Sbellard 
15454936004Sbellard     if (prot1 == 0) {
15554936004Sbellard         /* no page was there, so we allocate one */
15680210bcdSths         void *p = mmap(host_start, qemu_host_page_size, prot,
15754936004Sbellard                        flags | MAP_ANONYMOUS, -1, 0);
15880210bcdSths         if (p == MAP_FAILED)
15980210bcdSths             return -1;
16053a5960aSpbrook         prot1 = prot;
16154936004Sbellard     }
16254936004Sbellard     prot1 &= PAGE_BITS;
16354936004Sbellard 
16454936004Sbellard     prot_new = prot | prot1;
16554936004Sbellard     if (!(flags & MAP_ANONYMOUS)) {
16654936004Sbellard         /* msync() won't work here, so we return an error if write is
16754936004Sbellard            possible while it is a shared mapping */
16854936004Sbellard         if ((flags & MAP_TYPE) == MAP_SHARED &&
16954936004Sbellard             (prot & PROT_WRITE))
170ee636500SJuan Quintela             return -1;
17154936004Sbellard 
17254936004Sbellard         /* adjust protection to be able to read */
17354936004Sbellard         if (!(prot1 & PROT_WRITE))
17453a5960aSpbrook             mprotect(host_start, qemu_host_page_size, prot1 | PROT_WRITE);
17554936004Sbellard 
17654936004Sbellard         /* read the corresponding file data */
177fb7e378cSKirill A. Shutemov         if (pread(fd, g2h(start), end - start, offset) == -1)
178fb7e378cSKirill A. Shutemov             return -1;
17954936004Sbellard 
18054936004Sbellard         /* put final protection */
18154936004Sbellard         if (prot_new != (prot1 | PROT_WRITE))
18253a5960aSpbrook             mprotect(host_start, qemu_host_page_size, prot_new);
18354936004Sbellard     } else {
18454936004Sbellard         if (prot_new != prot1) {
18553a5960aSpbrook             mprotect(host_start, qemu_host_page_size, prot_new);
18654936004Sbellard         }
187e6deac9cSChen Gang         if (prot_new & PROT_WRITE) {
188e6deac9cSChen Gang             memset(g2h(start), 0, end - start);
189e6deac9cSChen Gang         }
19054936004Sbellard     }
19154936004Sbellard     return 0;
19254936004Sbellard }
19354936004Sbellard 
19414f24e14SRichard Henderson #if HOST_LONG_BITS == 64 && TARGET_ABI_BITS == 64
19514f24e14SRichard Henderson # define TASK_UNMAPPED_BASE  (1ul << 38)
196a03e2d42Sbellard #else
19714f24e14SRichard Henderson # define TASK_UNMAPPED_BASE  0x40000000
198a03e2d42Sbellard #endif
19959e9d91cSPeter Maydell abi_ulong mmap_next_start = TASK_UNMAPPED_BASE;
200a03e2d42Sbellard 
2010776590dSpbrook unsigned long last_brk;
2020776590dSpbrook 
20368a1c816SPaul Brook /* Subroutine of mmap_find_vma, used when we have pre-allocated a chunk
20468a1c816SPaul Brook    of guest address space.  */
20568a1c816SPaul Brook static abi_ulong mmap_find_vma_reserved(abi_ulong start, abi_ulong size)
20668a1c816SPaul Brook {
20768a1c816SPaul Brook     abi_ulong addr;
20859e9d91cSPeter Maydell     abi_ulong end_addr;
20968a1c816SPaul Brook     int prot;
21068a1c816SPaul Brook     int looped = 0;
21168a1c816SPaul Brook 
212b76f21a7SLaurent Vivier     if (size > reserved_va) {
21368a1c816SPaul Brook         return (abi_ulong)-1;
21468a1c816SPaul Brook     }
21568a1c816SPaul Brook 
21659e9d91cSPeter Maydell     size = HOST_PAGE_ALIGN(size);
21759e9d91cSPeter Maydell     end_addr = start + size;
218b76f21a7SLaurent Vivier     if (end_addr > reserved_va) {
219b76f21a7SLaurent Vivier         end_addr = reserved_va;
22059e9d91cSPeter Maydell     }
22159e9d91cSPeter Maydell     addr = end_addr - qemu_host_page_size;
22259e9d91cSPeter Maydell 
22359e9d91cSPeter Maydell     while (1) {
22459e9d91cSPeter Maydell         if (addr > end_addr) {
22568a1c816SPaul Brook             if (looped) {
22668a1c816SPaul Brook                 return (abi_ulong)-1;
22768a1c816SPaul Brook             }
228b76f21a7SLaurent Vivier             end_addr = reserved_va;
22959e9d91cSPeter Maydell             addr = end_addr - qemu_host_page_size;
23068a1c816SPaul Brook             looped = 1;
23168a1c816SPaul Brook             continue;
23268a1c816SPaul Brook         }
23368a1c816SPaul Brook         prot = page_get_flags(addr);
23468a1c816SPaul Brook         if (prot) {
23559e9d91cSPeter Maydell             end_addr = addr;
23668a1c816SPaul Brook         }
23759e9d91cSPeter Maydell         if (addr + size == end_addr) {
23859e9d91cSPeter Maydell             break;
23968a1c816SPaul Brook         }
24059e9d91cSPeter Maydell         addr -= qemu_host_page_size;
24159e9d91cSPeter Maydell     }
24259e9d91cSPeter Maydell 
24359e9d91cSPeter Maydell     if (start == mmap_next_start) {
24468a1c816SPaul Brook         mmap_next_start = addr;
24559e9d91cSPeter Maydell     }
24659e9d91cSPeter Maydell 
24759e9d91cSPeter Maydell     return addr;
24868a1c816SPaul Brook }
24968a1c816SPaul Brook 
250fe3b4152SKirill A. Shutemov /*
251fe3b4152SKirill A. Shutemov  * Find and reserve a free memory area of size 'size'. The search
252fe3b4152SKirill A. Shutemov  * starts at 'start'.
253fe3b4152SKirill A. Shutemov  * It must be called with mmap_lock() held.
254fe3b4152SKirill A. Shutemov  * Return -1 if error.
255a03e2d42Sbellard  */
2569ad197d9SRiku Voipio abi_ulong mmap_find_vma(abi_ulong start, abi_ulong size)
257a03e2d42Sbellard {
25814f24e14SRichard Henderson     void *ptr, *prev;
259fe3b4152SKirill A. Shutemov     abi_ulong addr;
26014f24e14SRichard Henderson     int wrapped, repeat;
261fe3b4152SKirill A. Shutemov 
262fe3b4152SKirill A. Shutemov     /* If 'start' == 0, then a default start address is used. */
26314f24e14SRichard Henderson     if (start == 0) {
264fe3b4152SKirill A. Shutemov         start = mmap_next_start;
26514f24e14SRichard Henderson     } else {
26614f24e14SRichard Henderson         start &= qemu_host_page_mask;
26714f24e14SRichard Henderson     }
26814f24e14SRichard Henderson 
26914f24e14SRichard Henderson     size = HOST_PAGE_ALIGN(size);
270fe3b4152SKirill A. Shutemov 
271b76f21a7SLaurent Vivier     if (reserved_va) {
27268a1c816SPaul Brook         return mmap_find_vma_reserved(start, size);
27368a1c816SPaul Brook     }
27468a1c816SPaul Brook 
275a03e2d42Sbellard     addr = start;
27614f24e14SRichard Henderson     wrapped = repeat = 0;
27714f24e14SRichard Henderson     prev = 0;
278fe3b4152SKirill A. Shutemov 
27914f24e14SRichard Henderson     for (;; prev = ptr) {
280fe3b4152SKirill A. Shutemov         /*
281fe3b4152SKirill A. Shutemov          * Reserve needed memory area to avoid a race.
282fe3b4152SKirill A. Shutemov          * It should be discarded using:
283fe3b4152SKirill A. Shutemov          *  - mmap() with MAP_FIXED flag
284fe3b4152SKirill A. Shutemov          *  - mremap() with MREMAP_FIXED flag
285fe3b4152SKirill A. Shutemov          *  - shmat() with SHM_REMAP flag
286fe3b4152SKirill A. Shutemov          */
28714f24e14SRichard Henderson         ptr = mmap(g2h(addr), size, PROT_NONE,
288fe3b4152SKirill A. Shutemov                    MAP_ANONYMOUS|MAP_PRIVATE|MAP_NORESERVE, -1, 0);
289fe3b4152SKirill A. Shutemov 
290fe3b4152SKirill A. Shutemov         /* ENOMEM, if host address space has no memory */
29114f24e14SRichard Henderson         if (ptr == MAP_FAILED) {
292a03e2d42Sbellard             return (abi_ulong)-1;
293a03e2d42Sbellard         }
294fe3b4152SKirill A. Shutemov 
29514f24e14SRichard Henderson         /* Count the number of sequential returns of the same address.
29614f24e14SRichard Henderson            This is used to modify the search algorithm below.  */
29714f24e14SRichard Henderson         repeat = (ptr == prev ? repeat + 1 : 0);
298fe3b4152SKirill A. Shutemov 
29914f24e14SRichard Henderson         if (h2g_valid(ptr + size - 1)) {
30014f24e14SRichard Henderson             addr = h2g(ptr);
30114f24e14SRichard Henderson 
30214f24e14SRichard Henderson             if ((addr & ~TARGET_PAGE_MASK) == 0) {
30314f24e14SRichard Henderson                 /* Success.  */
30414f24e14SRichard Henderson                 if (start == mmap_next_start && addr >= TASK_UNMAPPED_BASE) {
30514f24e14SRichard Henderson                     mmap_next_start = addr + size;
30614f24e14SRichard Henderson                 }
30714f24e14SRichard Henderson                 return addr;
30814f24e14SRichard Henderson             }
30914f24e14SRichard Henderson 
31014f24e14SRichard Henderson             /* The address is not properly aligned for the target.  */
31114f24e14SRichard Henderson             switch (repeat) {
31214f24e14SRichard Henderson             case 0:
31314f24e14SRichard Henderson                 /* Assume the result that the kernel gave us is the
31414f24e14SRichard Henderson                    first with enough free space, so start again at the
31514f24e14SRichard Henderson                    next higher target page.  */
31614f24e14SRichard Henderson                 addr = TARGET_PAGE_ALIGN(addr);
31714f24e14SRichard Henderson                 break;
31814f24e14SRichard Henderson             case 1:
31914f24e14SRichard Henderson                 /* Sometimes the kernel decides to perform the allocation
32014f24e14SRichard Henderson                    at the top end of memory instead.  */
32114f24e14SRichard Henderson                 addr &= TARGET_PAGE_MASK;
32214f24e14SRichard Henderson                 break;
32314f24e14SRichard Henderson             case 2:
32414f24e14SRichard Henderson                 /* Start over at low memory.  */
32514f24e14SRichard Henderson                 addr = 0;
32614f24e14SRichard Henderson                 break;
32714f24e14SRichard Henderson             default:
32814f24e14SRichard Henderson                 /* Fail.  This unaligned block must the last.  */
32914f24e14SRichard Henderson                 addr = -1;
33014f24e14SRichard Henderson                 break;
33114f24e14SRichard Henderson             }
33214f24e14SRichard Henderson         } else {
33314f24e14SRichard Henderson             /* Since the result the kernel gave didn't fit, start
33414f24e14SRichard Henderson                again at low memory.  If any repetition, fail.  */
33514f24e14SRichard Henderson             addr = (repeat ? -1 : 0);
33614f24e14SRichard Henderson         }
33714f24e14SRichard Henderson 
33814f24e14SRichard Henderson         /* Unmap and try again.  */
33914f24e14SRichard Henderson         munmap(ptr, size);
34014f24e14SRichard Henderson 
34114f24e14SRichard Henderson         /* ENOMEM if we checked the whole of the target address space.  */
342d0b3e4f5SBlue Swirl         if (addr == (abi_ulong)-1) {
34314f24e14SRichard Henderson             return (abi_ulong)-1;
34414f24e14SRichard Henderson         } else if (addr == 0) {
34514f24e14SRichard Henderson             if (wrapped) {
34614f24e14SRichard Henderson                 return (abi_ulong)-1;
34714f24e14SRichard Henderson             }
34814f24e14SRichard Henderson             wrapped = 1;
34914f24e14SRichard Henderson             /* Don't actually use 0 when wrapping, instead indicate
3508186e783SStefan Weil                that we'd truly like an allocation in low memory.  */
35114f24e14SRichard Henderson             addr = (mmap_min_addr > TARGET_PAGE_SIZE
35214f24e14SRichard Henderson                      ? TARGET_PAGE_ALIGN(mmap_min_addr)
35314f24e14SRichard Henderson                      : TARGET_PAGE_SIZE);
35414f24e14SRichard Henderson         } else if (wrapped && addr >= start) {
35514f24e14SRichard Henderson             return (abi_ulong)-1;
35614f24e14SRichard Henderson         }
35714f24e14SRichard Henderson     }
358a03e2d42Sbellard }
359a03e2d42Sbellard 
36054936004Sbellard /* NOTE: all the constants are the HOST ones */
361992f48a0Sblueswir1 abi_long target_mmap(abi_ulong start, abi_ulong len, int prot,
362992f48a0Sblueswir1                      int flags, int fd, abi_ulong offset)
36354936004Sbellard {
364992f48a0Sblueswir1     abi_ulong ret, end, real_start, real_end, retaddr, host_offset, host_len;
36554936004Sbellard 
366c8a706feSpbrook     mmap_lock();
36754936004Sbellard #ifdef DEBUG_MMAP
36854936004Sbellard     {
3690bf9e31aSBlue Swirl         printf("mmap: start=0x" TARGET_ABI_FMT_lx
3700bf9e31aSBlue Swirl                " len=0x" TARGET_ABI_FMT_lx " prot=%c%c%c flags=",
37154936004Sbellard                start, len,
37254936004Sbellard                prot & PROT_READ ? 'r' : '-',
37354936004Sbellard                prot & PROT_WRITE ? 'w' : '-',
37454936004Sbellard                prot & PROT_EXEC ? 'x' : '-');
37554936004Sbellard         if (flags & MAP_FIXED)
37654936004Sbellard             printf("MAP_FIXED ");
37754936004Sbellard         if (flags & MAP_ANONYMOUS)
37854936004Sbellard             printf("MAP_ANON ");
37954936004Sbellard         switch(flags & MAP_TYPE) {
38054936004Sbellard         case MAP_PRIVATE:
38154936004Sbellard             printf("MAP_PRIVATE ");
38254936004Sbellard             break;
38354936004Sbellard         case MAP_SHARED:
38454936004Sbellard             printf("MAP_SHARED ");
38554936004Sbellard             break;
38654936004Sbellard         default:
38754936004Sbellard             printf("[MAP_TYPE=0x%x] ", flags & MAP_TYPE);
38854936004Sbellard             break;
38954936004Sbellard         }
3900bf9e31aSBlue Swirl         printf("fd=%d offset=" TARGET_ABI_FMT_lx "\n", fd, offset);
39154936004Sbellard     }
39254936004Sbellard #endif
39354936004Sbellard 
394e89f07d3Spbrook     if (offset & ~TARGET_PAGE_MASK) {
395e89f07d3Spbrook         errno = EINVAL;
396c8a706feSpbrook         goto fail;
397e89f07d3Spbrook     }
39854936004Sbellard 
39954936004Sbellard     len = TARGET_PAGE_ALIGN(len);
40054936004Sbellard     if (len == 0)
401c8a706feSpbrook         goto the_end;
40253a5960aSpbrook     real_start = start & qemu_host_page_mask;
403a5e7ee46SRichard Henderson     host_offset = offset & qemu_host_page_mask;
404a5e7ee46SRichard Henderson 
405a5e7ee46SRichard Henderson     /* If the user is asking for the kernel to find a location, do that
406a5e7ee46SRichard Henderson        before we truncate the length for mapping files below.  */
407a5e7ee46SRichard Henderson     if (!(flags & MAP_FIXED)) {
408a5e7ee46SRichard Henderson         host_len = len + offset - host_offset;
409a5e7ee46SRichard Henderson         host_len = HOST_PAGE_ALIGN(host_len);
410a5e7ee46SRichard Henderson         start = mmap_find_vma(real_start, host_len);
411a5e7ee46SRichard Henderson         if (start == (abi_ulong)-1) {
412a5e7ee46SRichard Henderson             errno = ENOMEM;
413a5e7ee46SRichard Henderson             goto fail;
414a5e7ee46SRichard Henderson         }
415a5e7ee46SRichard Henderson     }
41654936004Sbellard 
41754c5a2aeSedgar_igl     /* When mapping files into a memory area larger than the file, accesses
41854c5a2aeSedgar_igl        to pages beyond the file size will cause a SIGBUS.
41954c5a2aeSedgar_igl 
42054c5a2aeSedgar_igl        For example, if mmaping a file of 100 bytes on a host with 4K pages
42154c5a2aeSedgar_igl        emulating a target with 8K pages, the target expects to be able to
42254c5a2aeSedgar_igl        access the first 8K. But the host will trap us on any access beyond
42354c5a2aeSedgar_igl        4K.
42454c5a2aeSedgar_igl 
42554c5a2aeSedgar_igl        When emulating a target with a larger page-size than the hosts, we
42654c5a2aeSedgar_igl        may need to truncate file maps at EOF and add extra anonymous pages
42754c5a2aeSedgar_igl        up to the targets page boundary.  */
42854c5a2aeSedgar_igl 
429*35f2fd04SMarc-André Lureau     if ((qemu_real_host_page_size < qemu_host_page_size) &&
430*35f2fd04SMarc-André Lureau         !(flags & MAP_ANONYMOUS)) {
43154c5a2aeSedgar_igl         struct stat sb;
43254c5a2aeSedgar_igl 
43354c5a2aeSedgar_igl        if (fstat (fd, &sb) == -1)
43454c5a2aeSedgar_igl            goto fail;
43554c5a2aeSedgar_igl 
43654c5a2aeSedgar_igl        /* Are we trying to create a map beyond EOF?.  */
43754c5a2aeSedgar_igl        if (offset + len > sb.st_size) {
43854c5a2aeSedgar_igl            /* If so, truncate the file map at eof aligned with
43954c5a2aeSedgar_igl               the hosts real pagesize. Additional anonymous maps
44054c5a2aeSedgar_igl               will be created beyond EOF.  */
4410c2d70c4SPaolo Bonzini            len = REAL_HOST_PAGE_ALIGN(sb.st_size - offset);
44254c5a2aeSedgar_igl        }
44354c5a2aeSedgar_igl     }
44454c5a2aeSedgar_igl 
44554936004Sbellard     if (!(flags & MAP_FIXED)) {
446a5e7ee46SRichard Henderson         unsigned long host_start;
447a03e2d42Sbellard         void *p;
448a5e7ee46SRichard Henderson 
44954936004Sbellard         host_len = len + offset - host_offset;
450a03e2d42Sbellard         host_len = HOST_PAGE_ALIGN(host_len);
451a5e7ee46SRichard Henderson 
452a03e2d42Sbellard         /* Note: we prefer to control the mapping address. It is
453a03e2d42Sbellard            especially important if qemu_host_page_size >
454a03e2d42Sbellard            qemu_real_host_page_size */
455a5e7ee46SRichard Henderson         p = mmap(g2h(start), host_len, prot,
456a5e7ee46SRichard Henderson                  flags | MAP_FIXED | MAP_ANONYMOUS, -1, 0);
45780210bcdSths         if (p == MAP_FAILED)
458c8a706feSpbrook             goto fail;
45954936004Sbellard         /* update start so that it points to the file position at 'offset' */
46080210bcdSths         host_start = (unsigned long)p;
46154c5a2aeSedgar_igl         if (!(flags & MAP_ANONYMOUS)) {
462a5e7ee46SRichard Henderson             p = mmap(g2h(start), len, prot,
46354c5a2aeSedgar_igl                      flags | MAP_FIXED, fd, host_offset);
4648384274eSJürg Billeter             if (p == MAP_FAILED) {
4658384274eSJürg Billeter                 munmap(g2h(start), host_len);
4668384274eSJürg Billeter                 goto fail;
4678384274eSJürg Billeter             }
46853a5960aSpbrook             host_start += offset - host_offset;
46954c5a2aeSedgar_igl         }
47053a5960aSpbrook         start = h2g(host_start);
471a03e2d42Sbellard     } else {
472e89f07d3Spbrook         if (start & ~TARGET_PAGE_MASK) {
473e89f07d3Spbrook             errno = EINVAL;
474c8a706feSpbrook             goto fail;
475e89f07d3Spbrook         }
47654936004Sbellard         end = start + len;
47753a5960aSpbrook         real_end = HOST_PAGE_ALIGN(end);
47854936004Sbellard 
47945bc1f52Saurel32 	/*
48045bc1f52Saurel32 	 * Test if requested memory area fits target address space
48145bc1f52Saurel32 	 * It can fail only on 64-bit host with 32-bit target.
48245bc1f52Saurel32 	 * On any other target/host host mmap() handles this error correctly.
48345bc1f52Saurel32 	 */
48445bc1f52Saurel32         if ((unsigned long)start + len - 1 > (abi_ulong) -1) {
48545bc1f52Saurel32             errno = EINVAL;
48645bc1f52Saurel32             goto fail;
48745bc1f52Saurel32         }
48845bc1f52Saurel32 
48954936004Sbellard         /* worst case: we cannot map the file because the offset is not
49054936004Sbellard            aligned, so we read it */
49154936004Sbellard         if (!(flags & MAP_ANONYMOUS) &&
49283fb7adfSbellard             (offset & ~qemu_host_page_mask) != (start & ~qemu_host_page_mask)) {
49354936004Sbellard             /* msync() won't work here, so we return an error if write is
49454936004Sbellard                possible while it is a shared mapping */
49554936004Sbellard             if ((flags & MAP_TYPE) == MAP_SHARED &&
496e89f07d3Spbrook                 (prot & PROT_WRITE)) {
497e89f07d3Spbrook                 errno = EINVAL;
498c8a706feSpbrook                 goto fail;
499e89f07d3Spbrook             }
50054936004Sbellard             retaddr = target_mmap(start, len, prot | PROT_WRITE,
50154936004Sbellard                                   MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS,
50254936004Sbellard                                   -1, 0);
50354936004Sbellard             if (retaddr == -1)
504c8a706feSpbrook                 goto fail;
505fb7e378cSKirill A. Shutemov             if (pread(fd, g2h(start), len, offset) == -1)
506fb7e378cSKirill A. Shutemov                 goto fail;
50754936004Sbellard             if (!(prot & PROT_WRITE)) {
50854936004Sbellard                 ret = target_mprotect(start, len, prot);
50986abac06SPaolo Bonzini                 assert(ret == 0);
51054936004Sbellard             }
51154936004Sbellard             goto the_end;
51254936004Sbellard         }
51354936004Sbellard 
51454936004Sbellard         /* handle the start of the mapping */
51553a5960aSpbrook         if (start > real_start) {
51653a5960aSpbrook             if (real_end == real_start + qemu_host_page_size) {
51754936004Sbellard                 /* one single host page */
51853a5960aSpbrook                 ret = mmap_frag(real_start, start, end,
51954936004Sbellard                                 prot, flags, fd, offset);
52054936004Sbellard                 if (ret == -1)
521c8a706feSpbrook                     goto fail;
52254936004Sbellard                 goto the_end1;
52354936004Sbellard             }
52453a5960aSpbrook             ret = mmap_frag(real_start, start, real_start + qemu_host_page_size,
52554936004Sbellard                             prot, flags, fd, offset);
52654936004Sbellard             if (ret == -1)
527c8a706feSpbrook                 goto fail;
52853a5960aSpbrook             real_start += qemu_host_page_size;
52954936004Sbellard         }
53054936004Sbellard         /* handle the end of the mapping */
53153a5960aSpbrook         if (end < real_end) {
53253a5960aSpbrook             ret = mmap_frag(real_end - qemu_host_page_size,
533530c0032SChen Gang                             real_end - qemu_host_page_size, end,
53454936004Sbellard                             prot, flags, fd,
53553a5960aSpbrook                             offset + real_end - qemu_host_page_size - start);
53654936004Sbellard             if (ret == -1)
537c8a706feSpbrook                 goto fail;
53853a5960aSpbrook             real_end -= qemu_host_page_size;
53954936004Sbellard         }
54054936004Sbellard 
54154936004Sbellard         /* map the middle (easier) */
54253a5960aSpbrook         if (real_start < real_end) {
54380210bcdSths             void *p;
5444a585ccbSbellard             unsigned long offset1;
5454a585ccbSbellard             if (flags & MAP_ANONYMOUS)
5464a585ccbSbellard                 offset1 = 0;
5474a585ccbSbellard             else
54853a5960aSpbrook                 offset1 = offset + real_start - start;
54980210bcdSths             p = mmap(g2h(real_start), real_end - real_start,
5504a585ccbSbellard                      prot, flags, fd, offset1);
55180210bcdSths             if (p == MAP_FAILED)
552c8a706feSpbrook                 goto fail;
55354936004Sbellard         }
554a03e2d42Sbellard     }
55554936004Sbellard  the_end1:
55654936004Sbellard     page_set_flags(start, start + len, prot | PAGE_VALID);
55754936004Sbellard  the_end:
55854936004Sbellard #ifdef DEBUG_MMAP
5590bf9e31aSBlue Swirl     printf("ret=0x" TARGET_ABI_FMT_lx "\n", start);
56054936004Sbellard     page_dump(stdout);
56154936004Sbellard     printf("\n");
56254936004Sbellard #endif
56335865339SPaolo Bonzini     tb_invalidate_phys_range(start, start + len);
564c8a706feSpbrook     mmap_unlock();
56554936004Sbellard     return start;
566c8a706feSpbrook fail:
567c8a706feSpbrook     mmap_unlock();
568c8a706feSpbrook     return -1;
56954936004Sbellard }
57054936004Sbellard 
57168a1c816SPaul Brook static void mmap_reserve(abi_ulong start, abi_ulong size)
57268a1c816SPaul Brook {
57368a1c816SPaul Brook     abi_ulong real_start;
57468a1c816SPaul Brook     abi_ulong real_end;
57568a1c816SPaul Brook     abi_ulong addr;
57668a1c816SPaul Brook     abi_ulong end;
57768a1c816SPaul Brook     int prot;
57868a1c816SPaul Brook 
57968a1c816SPaul Brook     real_start = start & qemu_host_page_mask;
58068a1c816SPaul Brook     real_end = HOST_PAGE_ALIGN(start + size);
58168a1c816SPaul Brook     end = start + size;
58268a1c816SPaul Brook     if (start > real_start) {
58368a1c816SPaul Brook         /* handle host page containing start */
58468a1c816SPaul Brook         prot = 0;
58568a1c816SPaul Brook         for (addr = real_start; addr < start; addr += TARGET_PAGE_SIZE) {
58668a1c816SPaul Brook             prot |= page_get_flags(addr);
58768a1c816SPaul Brook         }
58868a1c816SPaul Brook         if (real_end == real_start + qemu_host_page_size) {
58968a1c816SPaul Brook             for (addr = end; addr < real_end; addr += TARGET_PAGE_SIZE) {
59068a1c816SPaul Brook                 prot |= page_get_flags(addr);
59168a1c816SPaul Brook             }
59268a1c816SPaul Brook             end = real_end;
59368a1c816SPaul Brook         }
59468a1c816SPaul Brook         if (prot != 0)
59568a1c816SPaul Brook             real_start += qemu_host_page_size;
59668a1c816SPaul Brook     }
59768a1c816SPaul Brook     if (end < real_end) {
59868a1c816SPaul Brook         prot = 0;
59968a1c816SPaul Brook         for (addr = end; addr < real_end; addr += TARGET_PAGE_SIZE) {
60068a1c816SPaul Brook             prot |= page_get_flags(addr);
60168a1c816SPaul Brook         }
60268a1c816SPaul Brook         if (prot != 0)
60368a1c816SPaul Brook             real_end -= qemu_host_page_size;
60468a1c816SPaul Brook     }
60568a1c816SPaul Brook     if (real_start != real_end) {
60668a1c816SPaul Brook         mmap(g2h(real_start), real_end - real_start, PROT_NONE,
60768a1c816SPaul Brook                  MAP_FIXED | MAP_ANONYMOUS | MAP_PRIVATE | MAP_NORESERVE,
60868a1c816SPaul Brook                  -1, 0);
60968a1c816SPaul Brook     }
61068a1c816SPaul Brook }
61168a1c816SPaul Brook 
612992f48a0Sblueswir1 int target_munmap(abi_ulong start, abi_ulong len)
61354936004Sbellard {
614992f48a0Sblueswir1     abi_ulong end, real_start, real_end, addr;
61554936004Sbellard     int prot, ret;
61654936004Sbellard 
61754936004Sbellard #ifdef DEBUG_MMAP
6180bf9e31aSBlue Swirl     printf("munmap: start=0x" TARGET_ABI_FMT_lx " len=0x"
6190bf9e31aSBlue Swirl            TARGET_ABI_FMT_lx "\n",
6200bf9e31aSBlue Swirl            start, len);
62154936004Sbellard #endif
62254936004Sbellard     if (start & ~TARGET_PAGE_MASK)
62354936004Sbellard         return -EINVAL;
62454936004Sbellard     len = TARGET_PAGE_ALIGN(len);
62554936004Sbellard     if (len == 0)
62654936004Sbellard         return -EINVAL;
627c8a706feSpbrook     mmap_lock();
62854936004Sbellard     end = start + len;
62953a5960aSpbrook     real_start = start & qemu_host_page_mask;
63053a5960aSpbrook     real_end = HOST_PAGE_ALIGN(end);
63154936004Sbellard 
63253a5960aSpbrook     if (start > real_start) {
63354936004Sbellard         /* handle host page containing start */
63454936004Sbellard         prot = 0;
63553a5960aSpbrook         for(addr = real_start; addr < start; addr += TARGET_PAGE_SIZE) {
63654936004Sbellard             prot |= page_get_flags(addr);
63754936004Sbellard         }
63853a5960aSpbrook         if (real_end == real_start + qemu_host_page_size) {
63953a5960aSpbrook             for(addr = end; addr < real_end; addr += TARGET_PAGE_SIZE) {
640d418c81eSbellard                 prot |= page_get_flags(addr);
641d418c81eSbellard             }
64253a5960aSpbrook             end = real_end;
643d418c81eSbellard         }
64454936004Sbellard         if (prot != 0)
64553a5960aSpbrook             real_start += qemu_host_page_size;
64654936004Sbellard     }
64753a5960aSpbrook     if (end < real_end) {
64854936004Sbellard         prot = 0;
64953a5960aSpbrook         for(addr = end; addr < real_end; addr += TARGET_PAGE_SIZE) {
65054936004Sbellard             prot |= page_get_flags(addr);
65154936004Sbellard         }
65254936004Sbellard         if (prot != 0)
65353a5960aSpbrook             real_end -= qemu_host_page_size;
65454936004Sbellard     }
65554936004Sbellard 
656c8a706feSpbrook     ret = 0;
65754936004Sbellard     /* unmap what we can */
65853a5960aSpbrook     if (real_start < real_end) {
659b76f21a7SLaurent Vivier         if (reserved_va) {
66068a1c816SPaul Brook             mmap_reserve(real_start, real_end - real_start);
66168a1c816SPaul Brook         } else {
6624118a970Sj_mayer             ret = munmap(g2h(real_start), real_end - real_start);
66354936004Sbellard         }
66468a1c816SPaul Brook     }
66554936004Sbellard 
66677a8f1a5SAlexander Graf     if (ret == 0) {
66754936004Sbellard         page_set_flags(start, start + len, 0);
66835865339SPaolo Bonzini         tb_invalidate_phys_range(start, start + len);
66977a8f1a5SAlexander Graf     }
670c8a706feSpbrook     mmap_unlock();
671c8a706feSpbrook     return ret;
67254936004Sbellard }
67354936004Sbellard 
674992f48a0Sblueswir1 abi_long target_mremap(abi_ulong old_addr, abi_ulong old_size,
675992f48a0Sblueswir1                        abi_ulong new_size, unsigned long flags,
676992f48a0Sblueswir1                        abi_ulong new_addr)
67754936004Sbellard {
67854936004Sbellard     int prot;
679f19412a2Saurel32     void *host_addr;
68054936004Sbellard 
681c8a706feSpbrook     mmap_lock();
682f19412a2Saurel32 
68368a1c816SPaul Brook     if (flags & MREMAP_FIXED) {
68452956a9bSFelix Janda         host_addr = mremap(g2h(old_addr), old_size, new_size,
68552956a9bSFelix Janda                            flags, g2h(new_addr));
68668a1c816SPaul Brook 
687b76f21a7SLaurent Vivier         if (reserved_va && host_addr != MAP_FAILED) {
68868a1c816SPaul Brook             /* If new and old addresses overlap then the above mremap will
68968a1c816SPaul Brook                already have failed with EINVAL.  */
69068a1c816SPaul Brook             mmap_reserve(old_addr, old_size);
69168a1c816SPaul Brook         }
69268a1c816SPaul Brook     } else if (flags & MREMAP_MAYMOVE) {
693f19412a2Saurel32         abi_ulong mmap_start;
694f19412a2Saurel32 
695f19412a2Saurel32         mmap_start = mmap_find_vma(0, new_size);
696f19412a2Saurel32 
697f19412a2Saurel32         if (mmap_start == -1) {
698f19412a2Saurel32             errno = ENOMEM;
699f19412a2Saurel32             host_addr = MAP_FAILED;
70068a1c816SPaul Brook         } else {
70152956a9bSFelix Janda             host_addr = mremap(g2h(old_addr), old_size, new_size,
70252956a9bSFelix Janda                                flags | MREMAP_FIXED, g2h(mmap_start));
703b76f21a7SLaurent Vivier             if (reserved_va) {
70468a1c816SPaul Brook                 mmap_reserve(old_addr, old_size);
70568a1c816SPaul Brook             }
706c65ffe6dSamateur         }
7073af72a4dSblueswir1     } else {
70868a1c816SPaul Brook         int prot = 0;
709b76f21a7SLaurent Vivier         if (reserved_va && old_size < new_size) {
71068a1c816SPaul Brook             abi_ulong addr;
71168a1c816SPaul Brook             for (addr = old_addr + old_size;
71268a1c816SPaul Brook                  addr < old_addr + new_size;
71368a1c816SPaul Brook                  addr++) {
71468a1c816SPaul Brook                 prot |= page_get_flags(addr);
71568a1c816SPaul Brook             }
71668a1c816SPaul Brook         }
71768a1c816SPaul Brook         if (prot == 0) {
718f19412a2Saurel32             host_addr = mremap(g2h(old_addr), old_size, new_size, flags);
719b76f21a7SLaurent Vivier             if (host_addr != MAP_FAILED && reserved_va && old_size > new_size) {
72068a1c816SPaul Brook                 mmap_reserve(old_addr + old_size, new_size - old_size);
72168a1c816SPaul Brook             }
72268a1c816SPaul Brook         } else {
72368a1c816SPaul Brook             errno = ENOMEM;
72468a1c816SPaul Brook             host_addr = MAP_FAILED;
72568a1c816SPaul Brook         }
726f19412a2Saurel32         /* Check if address fits target address space */
727f19412a2Saurel32         if ((unsigned long)host_addr + new_size > (abi_ulong)-1) {
728f19412a2Saurel32             /* Revert mremap() changes */
729f19412a2Saurel32             host_addr = mremap(g2h(old_addr), new_size, old_size, flags);
730f19412a2Saurel32             errno = ENOMEM;
731f19412a2Saurel32             host_addr = MAP_FAILED;
732f19412a2Saurel32         }
733f19412a2Saurel32     }
734f19412a2Saurel32 
735f19412a2Saurel32     if (host_addr == MAP_FAILED) {
736c8a706feSpbrook         new_addr = -1;
737c8a706feSpbrook     } else {
738a5b85f79Sths         new_addr = h2g(host_addr);
73954936004Sbellard         prot = page_get_flags(old_addr);
74054936004Sbellard         page_set_flags(old_addr, old_addr + old_size, 0);
74154936004Sbellard         page_set_flags(new_addr, new_addr + new_size, prot | PAGE_VALID);
742c8a706feSpbrook     }
74335865339SPaolo Bonzini     tb_invalidate_phys_range(new_addr, new_addr + new_size);
744c8a706feSpbrook     mmap_unlock();
74554936004Sbellard     return new_addr;
74654936004Sbellard }
74754936004Sbellard 
748992f48a0Sblueswir1 int target_msync(abi_ulong start, abi_ulong len, int flags)
74954936004Sbellard {
750992f48a0Sblueswir1     abi_ulong end;
75154936004Sbellard 
75254936004Sbellard     if (start & ~TARGET_PAGE_MASK)
75354936004Sbellard         return -EINVAL;
75454936004Sbellard     len = TARGET_PAGE_ALIGN(len);
75554936004Sbellard     end = start + len;
756d418c81eSbellard     if (end < start)
757d418c81eSbellard         return -EINVAL;
758d418c81eSbellard     if (end == start)
759d418c81eSbellard         return 0;
76054936004Sbellard 
76183fb7adfSbellard     start &= qemu_host_page_mask;
76253a5960aSpbrook     return msync(g2h(start), end - start, flags);
76354936004Sbellard }
764