1/**
2 * \file
3 * \brief Hacky MMAP support for VFS.
4 * \bug The current implementation is a thin layer over an anonymous memobj.
5 *      It does not share memory, and does not propagate any updates.
6 */
7
8/*
9 * Copyright (c) 2011, ETH Zurich.
10 * All rights reserved.
11 *
12 * This file is distributed under the terms in the attached LICENSE file.
13 * If you do not find this file, copies can be found by writing to:
14 * ETH Zurich D-INFK, Haldeneggsteig 4, CH-8092 Zurich. Attn: Systems Group.
15 */
16
17#include <stdio.h>
18#include <stdlib.h>
19#include <string.h>
20#include <barrelfish/barrelfish.h>
21#include <barrelfish/memobj.h>
22#include <vfs/mmap.h>
23
24/**
25 * \brief Page fault handler
26 *
27 * \param memobj  The memory object
28 * \param region  The associated vregion
29 * \param offset  Offset into memory object of the page fault
30 * \param type    The fault type
31 */
32static errval_t pagefault(struct memobj *memobj, struct vregion *vregion,
33                          genvaddr_t offset, vm_fault_type_t type)
34{
35    errval_t err;
36    assert(memobj->type == MEMOBJ_VFS);
37    struct memobj_vfs *mv = (struct memobj_vfs *)memobj;
38    struct memobj_anon *anon = &mv->anon;
39    struct vspace *vspace = vregion_get_vspace(vregion);
40    struct pmap *pmap     = vspace_get_pmap(vspace);
41    genvaddr_t vregion_base  = vregion_get_base_addr(vregion);
42    genvaddr_t vregion_off   = vregion_get_offset(vregion);
43
44    assert(vregion_off == 0); // not sure if we handle this correctly
45
46    // Walk the ordered list to find the matching frame, but don't map it yet
47    struct memobj_frame_list *walk = anon->frame_list;
48    while (walk) {
49        if (offset >= walk->offset && offset < walk->offset + walk->size) {
50            break;
51        }
52        walk = walk->next;
53    }
54
55    if (walk == NULL) {
56        return LIB_ERR_MEMOBJ_WRONG_OFFSET;
57    }
58
59    genvaddr_t map_offset = vregion_off + walk->offset;
60    size_t nbytes = walk->size;
61
62    // how much do we need to read from the file?
63    if (map_offset >= mv->filesize) {
64        // nothing
65        goto do_map;
66    } else if (map_offset + nbytes > mv->filesize) {
67        // limit size of read to maximum mapping (rest is zero-filled)
68        nbytes = mv->filesize - map_offset;
69    }
70
71#if 0
72    debug_printf("fault at offset %lx, mapping at %lx-%lx from file data %lx-%lx\n",
73                 offset, vregion_base + map_offset,
74                 vregion_base + map_offset + walk->size,
75                 map_offset + mv->offset,
76                 map_offset + mv->offset + nbytes);
77#endif
78
79    // map frame writable at temporary location so that we can safely fill it
80    void *buf;
81    struct memobj *tmp_memobj = NULL;
82    struct vregion *tmp_vregion = NULL;
83    err = vspace_map_one_frame(&buf, walk->size, walk->frame,
84                               &tmp_memobj, &tmp_vregion);
85    if (err_is_fail(err)) {
86        DEBUG_ERR(err, "error setting up temp mapping in mmap pagefault handler\n");
87        return err; // XXX
88    }
89
90    // seek file handle
91    err = vfs_seek(mv->vh, VFS_SEEK_SET, map_offset + mv->offset);
92    if (err_is_fail(err)) {
93        return err;
94    }
95
96    // read contents into frame
97    size_t rsize, pos = 0;
98    do {
99        err = vfs_read(mv->vh, (char *)buf + pos, nbytes - pos, &rsize);
100        if (err_is_fail(err)) {
101            break;
102        }
103        pos += rsize;
104    } while(rsize > 0 && pos < nbytes);
105
106    // destroy temp mappings
107    // FIXME: the API for tearing down mappings is really unclear! is this sufficient?
108    err = vregion_destroy(tmp_vregion);
109    assert(err_is_ok(err));
110    err = memobj_destroy_one_frame(tmp_memobj);
111    assert(err_is_ok(err));
112    //free(tmp_vregion);
113    //free(tmp_memobj);
114
115do_map:
116    // map at target address with appropriate flags
117    err = pmap->f.map(pmap, vregion_base + map_offset, walk->frame, 0,
118                      walk->size, vregion_get_flags(vregion), NULL, NULL);
119    if (err_is_fail(err)) {
120        return err_push(err, LIB_ERR_PMAP_MAP);
121    }
122
123    return SYS_ERR_OK;
124}
125
126/**
127 * \brief Initialize
128 *
129 * \param memobj  The memory object
130 * \param size    Size of the memory region
131 * \param flags   Memory object specific flags
132 * \param vh      VFS handle for underlying file
133 * \param offset  Offset within file to start mapping
134 * \param filesize Size of file data to map, anything above this is zero-filled
135 */
136errval_t memobj_create_vfs(struct memobj_vfs *memobj, size_t size,
137                           memobj_flags_t flags, vfs_handle_t vh, off_t offset,
138                           size_t filesize)
139{
140    errval_t err;
141
142    // create anon memobj
143    err = memobj_create_anon(&memobj->anon, size, flags);
144    if (err_is_fail(err)) {
145        return err;
146    }
147
148    // replace pagefault handler with our own
149    ((struct memobj *)memobj)->f.pagefault = pagefault;
150
151    // reset type
152    ((struct memobj *)memobj)->type = MEMOBJ_VFS;
153
154    memobj->vh = vh;
155    memobj->offset = offset;
156    memobj->filesize = filesize;
157
158    return SYS_ERR_OK;
159}
160
161// FIXME: why aren't the destructors instance methods? -AB
162errval_t memobj_destroy_vfs(struct memobj *memobj)
163{
164    USER_PANIC("NYI");
165}
166
167// Kludge to push changes in VFS memobj back out to disk
168errval_t memobj_flush_vfs(struct memobj *memobj, struct vregion *vregion)
169{
170    errval_t err;
171
172    assert(memobj->type == MEMOBJ_VFS);
173
174    struct memobj_vfs *mv    = (struct memobj_vfs *)memobj;
175    struct vspace *vspace    = vregion_get_vspace(vregion);
176    struct pmap *pmap        = vspace_get_pmap(vspace);
177    genvaddr_t vregion_base  = vregion_get_base_addr(vregion);
178    lvaddr_t vregion_lbase   = vspace_genvaddr_to_lvaddr(vregion_base);
179    genvaddr_t vregion_off   = vregion_get_offset(vregion);
180
181    assert(vregion_off == 0); // not sure if we handle this correctly
182
183    /* TODO: mv->size instead of BASE_PAGE_SIZE?*/
184    for (genvaddr_t off = 0; off < mv->filesize ; off += BASE_PAGE_SIZE){
185        // For each page check if it's in memory
186        err = pmap->f.lookup(pmap, vregion_base + off, NULL);
187        if (err_is_fail(err)) {
188            continue; // Page not in memory
189#if 0 /* this optimisation may not be correct if flags were changed -AB */
190        } else if ((retflags & VREGION_FLAGS_WRITE) == 0) {
191            continue; // Not writable
192#endif
193        }
194
195        //TRACE("Flushing page at address: %lx\n", vregion_base + off);
196
197        // seek file handle
198        err = vfs_seek(mv->vh, VFS_SEEK_SET, off + mv->offset);
199        if (err_is_fail(err)) {
200            return err;
201        }
202
203        // write contents to file
204        size_t rsize, pos = 0;
205        size_t nbytes = mv->filesize - off;
206        if (nbytes > BASE_PAGE_SIZE) {
207            nbytes = BASE_PAGE_SIZE;
208        }
209
210        do {
211            err = vfs_write(mv->vh, (char *)vregion_lbase + off + pos,
212                            nbytes - pos, &rsize);
213            if (err_is_fail(err)) {
214                return err;
215            }
216            pos += rsize;
217        } while(rsize > 0 && pos < nbytes);
218        assert(pos==nbytes);
219    }
220
221    return SYS_ERR_OK;
222}
223
224/**
225 * \brief Wrapper to create and map a file object, optionally at a fixed address
226 *
227 * The memory object and vregion are returned so the user can call fill and
228 * pagefault on it to create actual mappings.
229 */
230static errval_t vspace_map_file_internal(genvaddr_t opt_base,
231                                         size_t opt_alignment,
232                                         size_t size, vregion_flags_t flags,
233                                         vfs_handle_t file, off_t offset,
234                                         size_t filesize,
235                                         struct vregion **ret_vregion,
236                                         struct memobj **ret_memobj)
237{
238    errval_t err1, err2;
239    struct memobj *memobj = NULL;
240    struct vregion *vregion = NULL;
241
242    // Allocate space
243    memobj = malloc(sizeof(struct memobj_vfs));
244    if (!memobj) {
245        err1 = LIB_ERR_MALLOC_FAIL;
246        goto error;
247    }
248    vregion = malloc(sizeof(struct vregion));
249    if (!vregion) {
250        err1 = LIB_ERR_MALLOC_FAIL;
251        goto error;
252    }
253
254    // Create a memobj and vregion
255    err1 = memobj_create_vfs((struct memobj_vfs *)memobj, size, 0, file, offset,
256                             filesize);
257    if (err_is_fail(err1)) {
258        err1 = err_push(err1, LIB_ERR_MEMOBJ_CREATE_VFS);
259        goto error;
260    }
261
262    if (opt_base != 0) {
263        err1 = vregion_map_fixed(vregion, get_current_vspace(), memobj, 0, size,
264                                 opt_base, flags);
265    } else if (opt_alignment != 0) {
266        err1 = vregion_map_aligned(vregion, get_current_vspace(), memobj, 0, size,
267                                   flags, opt_alignment);
268    } else {
269        err1 = vregion_map(vregion, get_current_vspace(), memobj, 0, size, flags);
270    }
271    if (err_is_fail(err1)) {
272        err1 = err_push(err1, LIB_ERR_VREGION_MAP);
273        goto error;
274    }
275
276    *ret_vregion = vregion;
277    *ret_memobj = memobj;
278
279    return SYS_ERR_OK;
280
281 error:
282    if (memobj) {
283        err2 = memobj_destroy_vfs(memobj);
284        if (err_is_fail(err2)) {
285            DEBUG_ERR(err2, "memobj_destroy_anon failed");
286        }
287        free(memobj);
288    }
289    if (vregion) {
290        err2 = vregion_destroy(vregion);
291        if (err_is_fail(err2)) {
292            DEBUG_ERR(err2, "vregion_destroy failed");
293        }
294        free(vregion);
295    }
296    return err1;
297}
298
299errval_t vspace_map_file(size_t size, vregion_flags_t flags,
300                         vfs_handle_t file, off_t offset, size_t filesize,
301                         struct vregion **ret_vregion,
302                         struct memobj **ret_memobj)
303{
304    return vspace_map_file_internal(0, 0, size, flags, file, offset, filesize,
305                                    ret_vregion, ret_memobj);
306}
307
308errval_t vspace_map_file_fixed(genvaddr_t base, size_t size,
309                               vregion_flags_t flags, vfs_handle_t file,
310                               off_t offset, size_t filesize,
311                               struct vregion **ret_vregion,
312                               struct memobj **ret_memobj)
313{
314    assert(base != 0);
315    return vspace_map_file_internal(base, 0, size, flags, file, offset, filesize,
316                                    ret_vregion, ret_memobj);
317}
318
319errval_t vspace_map_file_aligned(size_t alignment, size_t size,
320                                 vregion_flags_t flags, vfs_handle_t file,
321                                 off_t offset, size_t filesize,
322                                 struct vregion **ret_vregion,
323                                 struct memobj **ret_memobj)
324{
325    return vspace_map_file_internal(0, alignment, size, flags, file, offset,
326                                    filesize, ret_vregion, ret_memobj);
327}
328