1/*
2 * Copyright 2016, Data61
3 * Commonwealth Scientific and Industrial Research Organisation (CSIRO)
4 * ABN 41 687 119 230.
5 *
6 * This software may be distributed and modified according to the terms of
7 * the BSD 2-Clause license. Note that NO WARRANTY is provided.
8 * See "LICENSE_BSD2.txt" for details.
9 *
10 * @TAG(D61_BSD)
11 */
12
13#include <assert.h>
14#include <refos/vmlayout.h>
15#include <refos/error.h>
16#include <refos-io/mmap_segment.h>
17#include <refos-io/internal_state.h>
18#include <refos-util/dprintf.h>
19#include <refos-util/init.h>
20#include <refos-rpc/proc_client.h>
21#include <refos-rpc/proc_client_helper.h>
22
23#define REFOS_IO_INTERNAL_MMAP_PAGE_STATUS_BUFFER_SIZE 0x11000
24static char _refosioMMapPageStatusBuffer[REFOS_IO_INTERNAL_MMAP_PAGE_STATUS_BUFFER_SIZE];
25
26#define REFOS_IO_INTERNAL_MMAP_SEGMENT_BUFFER_SIZE 0x1000
27static char _refosioMMapSegmentStatusBuffer[REFOS_IO_INTERNAL_MMAP_SEGMENT_BUFFER_SIZE];
28
29void
30refosio_mmap_init(refos_io_mmap_segment_state_t *s)
31{
32    cbpool_init_static(&s->mmapRegionPageStatus, PROCESS_MMAP_LIMIT_SIZE_NPAGES,
33            _refosioMMapPageStatusBuffer, REFOS_IO_INTERNAL_MMAP_PAGE_STATUS_BUFFER_SIZE);
34    cbpool_init_static(&s->mmapRegionSegmentStatus, PROCESS_MMAP_SEGMENTS,
35            _refosioMMapSegmentStatusBuffer, REFOS_IO_INTERNAL_MMAP_SEGMENT_BUFFER_SIZE);
36}
37
38int
39refosio_mmap_segment_fill(refos_io_mmap_segment_state_t *s, uint32_t vaddrOffsetPage)
40{
41    int error = EINVALID;
42    assert(vaddrOffsetPage >= PROCESS_MMAP_BOT && vaddrOffsetPage < PROCESS_MMAP_TOP);
43    uint32_t segmentID = (PROCESS_MMAP_TOP - vaddrOffsetPage) /
44        (PROCESS_MMAP_SEGMENT_SIZE_NPAGES * REFOS_PAGE_SIZE);
45    assert(segmentID < PROCESS_MMAP_SEGMENTS);
46
47    if (cbpool_check_single(&s->mmapRegionSegmentStatus, segmentID)) {
48        /* This segment is already filled. Nothing to do here. */
49        return ESUCCESS;
50    }
51
52    /* Create the window. */
53    uint32_t vaddr = PROCESS_MMAP_TOP -
54            ((segmentID + 1) * (PROCESS_MMAP_SEGMENT_SIZE_NPAGES * REFOS_PAGE_SIZE));
55    assert(vaddr >= PROCESS_MMAP_BOT && vaddr < PROCESS_MMAP_TOP);
56
57    seL4_CPtr window = proc_create_mem_window(vaddr,
58            PROCESS_MMAP_SEGMENT_SIZE_NPAGES * REFOS_PAGE_SIZE);
59    if (!window || REFOS_GET_ERRNO() != ESUCCESS) {
60        seL4_DebugPrintf("mmap_segment_fill: Could not create window.\n");
61        return EINVALIDWINDOW;
62    }
63
64    /* Create the dataspace. */
65    seL4_CPtr dataspace = data_open(REFOS_PROCSERV_EP, "anon", 0, 0,
66            PROCESS_MMAP_SEGMENT_SIZE_NPAGES * REFOS_PAGE_SIZE, &error);
67    if (error) {
68        seL4_DebugPrintf("mmap_segment_fill: Could not create anon dspace.\n");
69        error = ENOMEM;
70        goto exit1;
71    }
72
73    /* Map the segment dataspace into the window. */
74    error = data_datamap(REFOS_PROCSERV_EP, dataspace, window, 0);
75    if (error != ESUCCESS) {
76        seL4_DebugPrintf("mmap_segment_fill: Could not map anon dspace.\n");
77        goto exit2;
78    }
79
80    /* Set the segment allocated status to TRUE. */
81    cbpool_set_single(&s->mmapRegionSegmentStatus, segmentID, true);
82
83    csfree_delete(dataspace);
84    csfree_delete(window);
85    return ESUCCESS;
86
87    /* Exit stack. */
88exit2:
89    data_close(REFOS_PROCSERV_EP, dataspace);
90    csfree_delete(dataspace);
91exit1:
92    proc_delete_mem_window(window);
93    csfree_delete(window);
94    return error;
95}
96
97int
98refosio_mmap_segment_release(refos_io_mmap_segment_state_t *s, uint32_t vaddrOffsetPage)
99{
100    assert(vaddrOffsetPage >= PROCESS_MMAP_BOT && vaddrOffsetPage < PROCESS_MMAP_TOP);
101    uint32_t segmentID = (PROCESS_MMAP_TOP - vaddrOffsetPage) /
102        (PROCESS_MMAP_SEGMENT_SIZE_NPAGES * REFOS_PAGE_SIZE);
103    assert(segmentID < PROCESS_MMAP_SEGMENTS);
104
105    if (!cbpool_check_single(&s->mmapRegionSegmentStatus, segmentID)) {
106        /* This segment is already released. Nothing to do here. */
107        return ESUCCESS;
108    }
109
110    /* Check that every page associated has neem release. Otherwise we don't unmap yet. */
111    for (int i = 0; i < PROCESS_MMAP_SEGMENT_SIZE_NPAGES; i++) {
112        uint32_t page = ( (PROCESS_MMAP_TOP - vaddrOffsetPage) / REFOS_PAGE_SIZE ) + i;
113        if (cbpool_check_single(&s->mmapRegionPageStatus, page)) {
114            /* A page is still mapped here. */
115            return EUNMAPFIRST;
116        }
117    }
118
119    /* Get the window. */
120    seL4_CPtr window = proc_get_mem_window(vaddrOffsetPage);
121    if (!window) {
122        /* Nothing mapped here. Nothing to do. */
123        seL4_DebugPrintf("mmap_segment_release: No window to release. Doing nothing.\n");
124        return ESUCCESS;
125    }
126
127    /* Get the associated dataspace and release it. */
128    refos_err_t refos_err = -EINVALID;
129    seL4_CPtr dspace = proc_get_mem_window_dspace(window, &refos_err);
130    if (refos_err != ESUCCESS) {
131        seL4_DebugPrintf("mmap_segment_fill: Failed to get window dataspace, error occued.\n");
132        return (int) refos_err;
133    }
134    if (dspace) {
135        int error = data_close(REFOS_PROCSERV_EP, dspace);
136        if (error) {
137            seL4_DebugPrintf("mmap_segment_fill: Failed delete dataspace.\n");
138        }
139        csfree_delete(dspace);
140    }
141
142    /* Finally, delete the window. */
143    int error = proc_delete_mem_window(window);
144    if (error) {
145        seL4_DebugPrintf("mmap_segment_fill: Failed to delete window, error occued.\n");
146    }
147    csfree_delete(window);
148
149    /* Set the segment allocated status back to FALSE. */
150    cbpool_set_single(&s->mmapRegionSegmentStatus, segmentID, false);
151    return ESUCCESS;
152}
153
154int
155refosio_mmap_anon(refos_io_mmap_segment_state_t *s, int npages, uint32_t *vaddrDest)
156{
157    if (!npages) {
158        /* Nothing to do here. */
159        return ESUCCESS;
160    }
161    assert(s);
162
163    /* Allocate the mmap pages from the page bitmap allocator. */
164    uint32_t vaddrOffsetPage = cbpool_alloc(&s->mmapRegionPageStatus, npages);
165    if (vaddrOffsetPage == CBPOOL_INVALID) {
166        seL4_DebugPrintf("mmap_anon: Could not allocate page region. Out of virtual memory.\n");
167        return ENOMEM;
168    }
169
170    /* Loop through every segment and fill it in. */
171    for (int i = 0; i < npages; i++) {
172        uint32_t vaddr = PROCESS_MMAP_TOP - ((vaddrOffsetPage + i + 1) * REFOS_PAGE_SIZE);
173
174        /* Fill in the segment. If the segment already is filled then it does nothing. */
175        int error = refosio_mmap_segment_fill(s, vaddr);
176        if (error != ESUCCESS) {
177            seL4_DebugPrintf("mmap_segment_fill failed. Cannot fully recover from this.\n");
178
179            /* All the segments up to this point have been allocated. All the segments here and
180               after have not been, so unset them. This prevents inconsistent state between the page
181               bitmap and actually mapped pages. */
182
183            cbpool_free(&s->mmapRegionPageStatus, vaddrOffsetPage + i, npages - i);
184            return error;
185        }
186    }
187
188    if (vaddrDest) {
189        (*vaddrDest) = PROCESS_MMAP_TOP - ((vaddrOffsetPage + npages) * REFOS_PAGE_SIZE);
190    }
191    return ESUCCESS;
192}
193
194int
195refosio_munmap_anon(refos_io_mmap_segment_state_t *s, uint32_t vaddr, int npages)
196{
197    if (npages == 0) {
198        /* Nothing to do here. */
199        return ESUCCESS;
200    }
201    if (vaddr >= PROCESS_MMAP_TOP) {
202        seL4_DebugPrintf("unmap_anon: invalid vaddr, too high.");
203        return EINVALIDPARAM;
204    }
205    if (vaddr < PROCESS_MMAP_BOT) {
206        seL4_DebugPrintf("unmap_anon: invalid vaddr, too low.");
207        return EINVALIDPARAM;
208    }
209
210    /* Free every page in the range. */
211    uint32_t vaddrOffsetPage = (PROCESS_MMAP_TOP - vaddr) / REFOS_PAGE_SIZE;
212    assert(vaddrOffsetPage < PROCESS_MMAP_LIMIT_SIZE_NPAGES);
213    cbpool_free(&s->mmapRegionPageStatus, vaddrOffsetPage, npages);
214
215    /* Loop through every segment and check for affected. */
216    for (int i = 0; i < npages; i++) {
217        uint32_t vaddr = PROCESS_MMAP_TOP - ((vaddrOffsetPage + i + 1) * REFOS_PAGE_SIZE);
218        int error = refosio_mmap_segment_release(s, vaddr);
219        if (error != ESUCCESS && error != EUNMAPFIRST) {
220            /* Best and easiest thing we can do here is just leak memory. */
221            seL4_DebugPrintf("refosio_mmap_segment_release failed. Leaked memory.\n");
222        }
223    }
224
225    return ESUCCESS;
226}