Cope with arch-specific page protection flags in mmap (Richard Purdie).
[qemu] / linux-user / mmap.c
1 /*
2  *  mmap support for qemu
3  *
4  *  Copyright (c) 2003 Fabrice Bellard
5  *
6  *  This program is free software; you can redistribute it and/or modify
7  *  it under the terms of the GNU General Public License as published by
8  *  the Free Software Foundation; either version 2 of the License, or
9  *  (at your option) any later version.
10  *
11  *  This program is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *  GNU General Public License for more details.
15  *
16  *  You should have received a copy of the GNU General Public License
17  *  along with this program; if not, write to the Free Software
18  *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19  */
20 #include <stdlib.h>
21 #include <stdio.h>
22 #include <stdarg.h>
23 #include <string.h>
24 #include <unistd.h>
25 #include <errno.h>
26 #include <sys/mman.h>
27
28 #include "qemu.h"
29
30 //#define DEBUG_MMAP
31
32 /* NOTE: all the constants are the HOST ones, but addresses are target. */
33 int target_mprotect(abi_ulong start, abi_ulong len, int prot)
34 {
35     abi_ulong end, host_start, host_end, addr;
36     int prot1, ret;
37
38 #ifdef DEBUG_MMAP
39     printf("mprotect: start=0x" TARGET_FMT_lx
40            "len=0x" TARGET_FMT_lx " prot=%c%c%c\n", start, len,
41            prot & PROT_READ ? 'r' : '-',
42            prot & PROT_WRITE ? 'w' : '-',
43            prot & PROT_EXEC ? 'x' : '-');
44 #endif
45
46     if ((start & ~TARGET_PAGE_MASK) != 0)
47         return -EINVAL;
48     len = TARGET_PAGE_ALIGN(len);
49     end = start + len;
50     if (end < start)
51         return -EINVAL;
52     prot &= PROT_READ | PROT_WRITE | PROT_EXEC;
53     if (len == 0)
54         return 0;
55
56     host_start = start & qemu_host_page_mask;
57     host_end = HOST_PAGE_ALIGN(end);
58     if (start > host_start) {
59         /* handle host page containing start */
60         prot1 = prot;
61         for(addr = host_start; addr < start; addr += TARGET_PAGE_SIZE) {
62             prot1 |= page_get_flags(addr);
63         }
64         if (host_end == host_start + qemu_host_page_size) {
65             for(addr = end; addr < host_end; addr += TARGET_PAGE_SIZE) {
66                 prot1 |= page_get_flags(addr);
67             }
68             end = host_end;
69         }
70         ret = mprotect(g2h(host_start), qemu_host_page_size, prot1 & PAGE_BITS);
71         if (ret != 0)
72             return ret;
73         host_start += qemu_host_page_size;
74     }
75     if (end < host_end) {
76         prot1 = prot;
77         for(addr = end; addr < host_end; addr += TARGET_PAGE_SIZE) {
78             prot1 |= page_get_flags(addr);
79         }
80         ret = mprotect(g2h(host_end - qemu_host_page_size), qemu_host_page_size,
81                        prot1 & PAGE_BITS);
82         if (ret != 0)
83             return ret;
84         host_end -= qemu_host_page_size;
85     }
86
87     /* handle the pages in the middle */
88     if (host_start < host_end) {
89         ret = mprotect(g2h(host_start), host_end - host_start, prot);
90         if (ret != 0)
91             return ret;
92     }
93     page_set_flags(start, start + len, prot | PAGE_VALID);
94     return 0;
95 }
96
97 /* map an incomplete host page */
98 static int mmap_frag(abi_ulong real_start,
99                      abi_ulong start, abi_ulong end,
100                      int prot, int flags, int fd, abi_ulong offset)
101 {
102     abi_ulong real_end, addr;
103     void *host_start;
104     int prot1, prot_new;
105
106     real_end = real_start + qemu_host_page_size;
107     host_start = g2h(real_start);
108
109     /* get the protection of the target pages outside the mapping */
110     prot1 = 0;
111     for(addr = real_start; addr < real_end; addr++) {
112         if (addr < start || addr >= end)
113             prot1 |= page_get_flags(addr);
114     }
115
116     if (prot1 == 0) {
117         /* no page was there, so we allocate one */
118         void *p = mmap(host_start, qemu_host_page_size, prot,
119                        flags | MAP_ANONYMOUS, -1, 0);
120         if (p == MAP_FAILED)
121             return -1;
122         prot1 = prot;
123     }
124     prot1 &= PAGE_BITS;
125
126     prot_new = prot | prot1;
127     if (!(flags & MAP_ANONYMOUS)) {
128         /* msync() won't work here, so we return an error if write is
129            possible while it is a shared mapping */
130         if ((flags & MAP_TYPE) == MAP_SHARED &&
131             (prot & PROT_WRITE))
132             return -EINVAL;
133
134         /* adjust protection to be able to read */
135         if (!(prot1 & PROT_WRITE))
136             mprotect(host_start, qemu_host_page_size, prot1 | PROT_WRITE);
137
138         /* read the corresponding file data */
139         pread(fd, g2h(start), end - start, offset);
140
141         /* put final protection */
142         if (prot_new != (prot1 | PROT_WRITE))
143             mprotect(host_start, qemu_host_page_size, prot_new);
144     } else {
145         /* just update the protection */
146         if (prot_new != prot1) {
147             mprotect(host_start, qemu_host_page_size, prot_new);
148         }
149     }
150     return 0;
151 }
152
153 #if defined(__CYGWIN__)
154 /* Cygwin doesn't have a whole lot of address space.  */
155 static abi_ulong mmap_next_start = 0x18000000;
156 #else
157 static abi_ulong mmap_next_start = 0x40000000;
158 #endif
159
160 /* find a free memory area of size 'size'. The search starts at
161    'start'. If 'start' == 0, then a default start address is used.
162    Return -1 if error.
163 */
164 /* page_init() marks pages used by the host as reserved to be sure not
165    to use them. */
166 static abi_ulong mmap_find_vma(abi_ulong start, abi_ulong size)
167 {
168     abi_ulong addr, addr1, addr_start;
169     int prot;
170
171     size = HOST_PAGE_ALIGN(size);
172     start = start & qemu_host_page_mask;
173     addr = start;
174     if (addr == 0)
175         addr = mmap_next_start;
176     addr_start = addr;
177     for(;;) {
178         prot = 0;
179         for(addr1 = addr; addr1 < (addr + size); addr1 += TARGET_PAGE_SIZE) {
180             prot |= page_get_flags(addr1);
181         }
182         if (prot == 0)
183             break;
184         addr += qemu_host_page_size;
185         /* we found nothing */
186         if (addr == addr_start)
187             return (abi_ulong)-1;
188     }
189     if (start == 0)
190         mmap_next_start = addr + size;
191     return addr;
192 }
193
194 /* NOTE: all the constants are the HOST ones */
195 abi_long target_mmap(abi_ulong start, abi_ulong len, int prot,
196                      int flags, int fd, abi_ulong offset)
197 {
198     abi_ulong ret, end, real_start, real_end, retaddr, host_offset, host_len;
199     unsigned long host_start;
200
201 #ifdef DEBUG_MMAP
202     {
203         printf("mmap: start=0x" TARGET_FMT_lx
204                " len=0x" TARGET_FMT_lx " prot=%c%c%c flags=",
205                start, len,
206                prot & PROT_READ ? 'r' : '-',
207                prot & PROT_WRITE ? 'w' : '-',
208                prot & PROT_EXEC ? 'x' : '-');
209         if (flags & MAP_FIXED)
210             printf("MAP_FIXED ");
211         if (flags & MAP_ANONYMOUS)
212             printf("MAP_ANON ");
213         switch(flags & MAP_TYPE) {
214         case MAP_PRIVATE:
215             printf("MAP_PRIVATE ");
216             break;
217         case MAP_SHARED:
218             printf("MAP_SHARED ");
219             break;
220         default:
221             printf("[MAP_TYPE=0x%x] ", flags & MAP_TYPE);
222             break;
223         }
224         printf("fd=%d offset=" TARGET_FMT_lx "\n", fd, offset);
225     }
226 #endif
227
228     if (offset & ~TARGET_PAGE_MASK) {
229         errno = EINVAL;
230         return -1;
231     }
232
233     len = TARGET_PAGE_ALIGN(len);
234     if (len == 0)
235         return start;
236     real_start = start & qemu_host_page_mask;
237
238     if (!(flags & MAP_FIXED)) {
239         abi_ulong mmap_start;
240         void *p;
241         host_offset = offset & qemu_host_page_mask;
242         host_len = len + offset - host_offset;
243         host_len = HOST_PAGE_ALIGN(host_len);
244         mmap_start = mmap_find_vma(real_start, host_len);
245         if (mmap_start == (abi_ulong)-1) {
246             errno = ENOMEM;
247             return -1;
248         }
249         /* Note: we prefer to control the mapping address. It is
250            especially important if qemu_host_page_size >
251            qemu_real_host_page_size */
252         p = mmap(g2h(mmap_start),
253                  host_len, prot, flags | MAP_FIXED, fd, host_offset);
254         if (p == MAP_FAILED)
255             return -1;
256         /* update start so that it points to the file position at 'offset' */
257         host_start = (unsigned long)p;
258         if (!(flags & MAP_ANONYMOUS))
259             host_start += offset - host_offset;
260         start = h2g(host_start);
261     } else {
262         if (start & ~TARGET_PAGE_MASK) {
263             errno = EINVAL;
264             return -1;
265         }
266         end = start + len;
267         real_end = HOST_PAGE_ALIGN(end);
268         
269         /* worst case: we cannot map the file because the offset is not
270            aligned, so we read it */
271         if (!(flags & MAP_ANONYMOUS) &&
272             (offset & ~qemu_host_page_mask) != (start & ~qemu_host_page_mask)) {
273             /* msync() won't work here, so we return an error if write is
274                possible while it is a shared mapping */
275             if ((flags & MAP_TYPE) == MAP_SHARED &&
276                 (prot & PROT_WRITE)) {
277                 errno = EINVAL;
278                 return -1;
279             }
280             retaddr = target_mmap(start, len, prot | PROT_WRITE,
281                                   MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS,
282                                   -1, 0);
283             if (retaddr == -1)
284                 return -1;
285             pread(fd, g2h(start), len, offset);
286             if (!(prot & PROT_WRITE)) {
287                 ret = target_mprotect(start, len, prot);
288                 if (ret != 0)
289                     return ret;
290             }
291             goto the_end;
292         }
293         
294         /* handle the start of the mapping */
295         if (start > real_start) {
296             if (real_end == real_start + qemu_host_page_size) {
297                 /* one single host page */
298                 ret = mmap_frag(real_start, start, end,
299                                 prot, flags, fd, offset);
300                 if (ret == -1)
301                     return ret;
302                 goto the_end1;
303             }
304             ret = mmap_frag(real_start, start, real_start + qemu_host_page_size,
305                             prot, flags, fd, offset);
306             if (ret == -1)
307                 return ret;
308             real_start += qemu_host_page_size;
309         }
310         /* handle the end of the mapping */
311         if (end < real_end) {
312             ret = mmap_frag(real_end - qemu_host_page_size,
313                             real_end - qemu_host_page_size, real_end,
314                             prot, flags, fd,
315                             offset + real_end - qemu_host_page_size - start);
316             if (ret == -1)
317                 return -1;
318             real_end -= qemu_host_page_size;
319         }
320
321         /* map the middle (easier) */
322         if (real_start < real_end) {
323             void *p;
324             unsigned long offset1;
325             if (flags & MAP_ANONYMOUS)
326                 offset1 = 0;
327             else
328                 offset1 = offset + real_start - start;
329             p = mmap(g2h(real_start), real_end - real_start,
330                      prot, flags, fd, offset1);
331             if (p == MAP_FAILED)
332                 return -1;
333         }
334     }
335  the_end1:
336     page_set_flags(start, start + len, prot | PAGE_VALID);
337  the_end:
338 #ifdef DEBUG_MMAP
339     printf("ret=0x" TARGET_FMT_lx "\n", start);
340     page_dump(stdout);
341     printf("\n");
342 #endif
343     return start;
344 }
345
346 int target_munmap(abi_ulong start, abi_ulong len)
347 {
348     abi_ulong end, real_start, real_end, addr;
349     int prot, ret;
350
351 #ifdef DEBUG_MMAP
352     printf("munmap: start=0x%lx len=0x%lx\n", start, len);
353 #endif
354     if (start & ~TARGET_PAGE_MASK)
355         return -EINVAL;
356     len = TARGET_PAGE_ALIGN(len);
357     if (len == 0)
358         return -EINVAL;
359     end = start + len;
360     real_start = start & qemu_host_page_mask;
361     real_end = HOST_PAGE_ALIGN(end);
362
363     if (start > real_start) {
364         /* handle host page containing start */
365         prot = 0;
366         for(addr = real_start; addr < start; addr += TARGET_PAGE_SIZE) {
367             prot |= page_get_flags(addr);
368         }
369         if (real_end == real_start + qemu_host_page_size) {
370             for(addr = end; addr < real_end; addr += TARGET_PAGE_SIZE) {
371                 prot |= page_get_flags(addr);
372             }
373             end = real_end;
374         }
375         if (prot != 0)
376             real_start += qemu_host_page_size;
377     }
378     if (end < real_end) {
379         prot = 0;
380         for(addr = end; addr < real_end; addr += TARGET_PAGE_SIZE) {
381             prot |= page_get_flags(addr);
382         }
383         if (prot != 0)
384             real_end -= qemu_host_page_size;
385     }
386
387     /* unmap what we can */
388     if (real_start < real_end) {
389         ret = munmap(g2h(real_start), real_end - real_start);
390         if (ret != 0)
391             return ret;
392     }
393
394     page_set_flags(start, start + len, 0);
395     return 0;
396 }
397
398 /* XXX: currently, we only handle MAP_ANONYMOUS and not MAP_FIXED
399    blocks which have been allocated starting on a host page */
400 abi_long target_mremap(abi_ulong old_addr, abi_ulong old_size,
401                        abi_ulong new_size, unsigned long flags,
402                        abi_ulong new_addr)
403 {
404     int prot;
405     unsigned long host_addr;
406
407     /* XXX: use 5 args syscall */
408     host_addr = (long)mremap(g2h(old_addr), old_size, new_size, flags);
409     if (host_addr == -1)
410         return -1;
411     new_addr = h2g(host_addr);
412     prot = page_get_flags(old_addr);
413     page_set_flags(old_addr, old_addr + old_size, 0);
414     page_set_flags(new_addr, new_addr + new_size, prot | PAGE_VALID);
415     return new_addr;
416 }
417
418 int target_msync(abi_ulong start, abi_ulong len, int flags)
419 {
420     abi_ulong end;
421
422     if (start & ~TARGET_PAGE_MASK)
423         return -EINVAL;
424     len = TARGET_PAGE_ALIGN(len);
425     end = start + len;
426     if (end < start)
427         return -EINVAL;
428     if (end == start)
429         return 0;
430
431     start &= qemu_host_page_mask;
432     return msync(g2h(start), end - start, flags);
433 }