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 <stdio.h>
14#include <stdbool.h>
15#include <stdint.h>
16#include <sel4/sel4.h>
17#include <sel4/messages.h>
18#include <sel4utils/arch/util.h>
19#include <refos-rpc/proc_common.h>
20#include <refos/error.h>
21
22#include "fault_handler.h"
23#include "dispatcher.h"
24#include "../state.h"
25#include "../badge.h"
26#include "../system/addrspace/vspace.h"
27#include "../system/process/process.h"
28#include <refos/refos.h>
29
30/*! @file
31    @brief Process server fault dispatcher which handles VM faults. */
32
33/*! @brief Temporary internal VM fault message info struct. */
34struct procserv_vmfault_msg {
35    /*! The faulting program's process control block. */
36    struct proc_pcb *pcb;
37    /*! The program counter at the faulting instruction. */
38    vaddr_t pc;
39    /* The faulting address. */
40    vaddr_t faultAddr;
41    /*! Whether this fault was caused by a data access or an instruction.  */
42    seL4_Word instruction;
43    /*! The arch-dependant fault status register passed in from the kernel. */
44    seL4_Word fsr;
45    /*! True if this fault is a read fault, false if it is a write fault. */
46    bool read;
47};
48
49/*! @brief Helper function to print a rather colourful and verbose segmentation fault message.
50    @param message Text message containing fault reason.
51    @param f The fault message info structure.
52*/
53static void
54output_segmentation_fault(const char* message, struct procserv_vmfault_msg *f)
55{
56    ROS_ERROR("��������� RefOS Program PID %d [%s] blocked with Segmentation fault ���_��� ���������",
57            f->pcb->pid, f->pcb->debugProcessName);
58    ROS_ERROR("    %s %s fault at 0x%x, Instruction Pointer 0x%x, Fault Status Register 0x%x",
59            f->instruction ? "Instruction" : "Data", f->read ? "read" : "write",
60            f->faultAddr, f->pc, f->fsr);
61    ROS_ERROR("    Error Message: " COLOUR_R "%s" COLOUR_RESET, message);
62    ROS_ERROR("    Thread will be blocked indefinitely.");
63}
64
65/*! @brief Helper function to delegate fault message to an external endpoint.
66
67    Writes the given message to the delegator's notification ring buffer,  and then sends an async
68    notification along the EP the delegator has set up previously.  This helper function is used for
69    every kind of notification.
70
71    @param f The fault message info structure.
72    @param delegationPCB The PCB structure of the delegator recieving the notification.
73    @param delegationEP The async endpoint of delegator to notify.
74    @param vmFaultNotification The VM fault noficfiation message contents.
75    @param saveReply Whether to save the falunt client's reply EP.
76*/
77static void
78fault_delegate_notification(struct procserv_vmfault_msg *f, struct proc_pcb *delegationPCB,
79        cspacepath_t delegationEP, struct proc_notification vmFaultNotification, bool saveReply)
80{
81    assert(f && f->pcb);
82    assert(delegationPCB && delegationPCB->magic == REFOS_PCB_MAGIC);
83
84    if (!delegationPCB->notificationBuffer) {
85        printf("PID notif buffer NULL! %d\n", delegationPCB->pid);
86        output_segmentation_fault("Delegator dataspace server has no notification buffer.", f);
87        return;
88    }
89
90    /* Save the caller reply cap to reply to later. */
91    if (saveReply) {
92        int error = proc_save_caller(f->pcb);
93        if (error) {
94            output_segmentation_fault("Failed to save caller reply cap.", f);
95            return;
96        }
97    }
98
99    /* Append notification to pager's notification buffer. */
100    int error = rb_write(delegationPCB->notificationBuffer, (char*)(&vmFaultNotification),
101            sizeof(vmFaultNotification));
102    if (error) {
103        output_segmentation_fault("Failed to write VM fault notification to buffer.", f);
104        return;
105    }
106
107    /* Notify the pager of this fault. */
108    dispatcher_notify(delegationEP.capPtr);
109}
110
111/* ----------------------------- Proc Server fault handler functions ---------------------------- */
112
113/*! @brief Handles faults on windows mapped to anonymous memory.
114
115    This function is responsible for handling VM faults on windows which have been mapped to the
116    process server's own anonymous dataspaces, including ones that have been content-initialised.
117
118    If the dataspace has been set to content-initialised, then we will need to delegate and save the
119    reply cap to reply to it once the content has been initialised. If it has not been initialised
120    we simply map the dataspace page and reply.
121
122    @param m The recieved IPC fault message from the kernel.
123    @param f The VM fault message info struct.
124    @param aw Found associated window of the faulting address & client.
125    @param window The window structure of the faulting address & client.
126    @return ESUCCESS on success, refos_err_t otherwise.
127*/
128static int
129handle_vm_fault_dspace(struct procserv_msg *m, struct procserv_vmfault_msg *f,
130        struct w_associated_window *aw, struct w_window *window)
131{
132    assert(f && f->pcb);
133    assert(aw && window && window->mode == W_MODE_ANONYMOUS);
134
135    vaddr_t dspaceOffset = (f->faultAddr + window->ramDataspaceOffset) -
136                           REFOS_PAGE_ALIGN(aw->offset);
137    struct ram_dspace *dspace = window->ramDataspace;
138    assert(dspace && dspace->magic == RAM_DATASPACE_MAGIC);
139
140    dvprintf("# PID %d VM fault ������������������ anon RAM dspace %d\n", f->pcb->pid, dspace->ID);
141
142    if (dspace->contentInitEnabled) {
143        /* Data space is backed by external content. Content initialisation delegation. */
144        int contentInitState =  ram_dspace_need_content_init(dspace, dspaceOffset);
145        if (contentInitState < 0) {
146            output_segmentation_fault("Failed to retrieve content-init state.", f);
147            return EINVALID;
148        }
149        if (contentInitState == true) {
150            /* Content has not yet been initialised so we delegate. */
151            if (f->faultAddr + window->ramDataspaceOffset >= aw->offset + aw->size) {
152                output_segmentation_fault("Fault address out of range!", f);
153                return EINVALID;
154            }
155
156            /* Find the pager's PCB. */
157            assert(dspace->contentInitPID != PID_NULL);
158            struct proc_pcb* cinitPCB = pid_get_pcb(&procServ.PIDList, dspace->contentInitPID);
159            if (!cinitPCB) {
160                output_segmentation_fault("Invalid content initialiser PID.", f);
161                return EINVALID;
162            }
163            if (!dspace->contentInitEP.capPtr) {
164                output_segmentation_fault("Invalid content-init endpoint!", f);
165                return EINVALID;
166            }
167
168            /* Save the reply endpoint. */
169            int error = ram_dspace_add_content_init_waiter_save_current_caller(dspace,
170                    dspaceOffset);
171            if (error != ESUCCESS) {
172                output_segmentation_fault("Failed to save reply cap as dspace waiter!", f);
173                return EINVALID;
174            }
175
176            /* Set up and send the fault notification. */
177            struct proc_notification vmFaultNotification;
178            vmFaultNotification.magic = PROCSERV_NOTIFICATION_MAGIC;
179            vmFaultNotification.label = PROCSERV_NOTIFY_CONTENT_INIT;
180            vmFaultNotification.arg[0] = dspace->ID;
181            vmFaultNotification.arg[1] = REFOS_PAGE_ALIGN(dspaceOffset);
182
183            fault_delegate_notification(f, cinitPCB, dspace->contentInitEP, vmFaultNotification,
184                                        false);
185
186            /* Return an error here to avoid resuming the client. */
187            return EDELEGATED;
188        }
189
190        /* Fallthrough to normal dspace mapping if content-init state is set to already provided. */
191    }
192
193    /* Get the page at the dataspaceOffset into the dataspace. */
194    seL4_CPtr frame = ram_dspace_get_page(dspace, dspaceOffset);
195    if (!frame) {
196        output_segmentation_fault("Out of memory to allocate page or read off end of dspace.", f);
197        return ENOMEM;
198    }
199
200    /* Map this frame into the client process's page directory. */
201    int error = vs_map(&f->pcb->vspace, f->faultAddr, &frame, 1);
202    if (error != ESUCCESS) {
203        output_segmentation_fault("Failed to map frame into client's vspace at faultAddr.", f);
204        return error;
205    }
206
207    return ESUCCESS;
208}
209
210/*! \brief Handles faults on windows set to external pager.
211
212    This functions handles the VM faults which results in a window that has been set up to be in
213    external paged mode. we simply delegate to the external pager and they'll call data_datamap
214    back on us and take are of the rest.
215
216    @param m The recieved IPC fault message from the kernel.
217    @param f The VM fault message info struct.
218    @param aw Found associated window of the faulting address & client.
219    @param window The window structure of the faulting address & client.
220    @return ESUCCESS on success, refos_err_t otherwise.
221*/
222static int
223handle_vm_fault_pager(struct procserv_msg *m, struct procserv_vmfault_msg *f,
224        struct w_associated_window *aw, struct w_window *window)
225{
226    assert(f && f->pcb);
227    assert(aw && window && window->mode == W_MODE_PAGER);
228
229    /* Set up and send the fault notification. */
230    struct proc_notification vmFaultNotification;
231    vmFaultNotification.magic = PROCSERV_NOTIFICATION_MAGIC;
232    vmFaultNotification.label = PROCSERV_NOTIFY_FAULT_DELEGATION;
233    vmFaultNotification.arg[0] = window->wID;
234    vmFaultNotification.arg[1] = window->size;
235    vmFaultNotification.arg[2] = f->faultAddr;
236    vmFaultNotification.arg[3] = aw->offset;
237    vmFaultNotification.arg[4] = f->instruction;
238    vmFaultNotification.arg[5] = window->permissions;
239    vmFaultNotification.arg[6] = f->pc;
240
241    /* Find the pager's PCB. */
242    assert(window->pagerPID != PID_NULL);
243    struct proc_pcb* pagerPCB = pid_get_pcb(&procServ.PIDList, window->pagerPID);
244    if (!pagerPCB) {
245        output_segmentation_fault("Invalid pager PID.", f);
246        return EINVALID;
247    }
248
249    /* Send the delegation notification. */
250    fault_delegate_notification(f, pagerPCB, window->pager, vmFaultNotification, true);
251
252    /* Return an error here to avoid resuming the client. */
253    return EDELEGATED;
254}
255
256/*! @brief Handles client VM fault messages sent by the kernel.
257
258    Handles the VM fault message by looking up the details of the window that it faulted in, and
259    decides whether this fault should be delegated to an external dataspace server for paging
260    or content initalisation, or be handled internally by the process server's own dataspace
261    implementation for RAM, or is an invalid memory access.
262
263    In the case of an invalid memory access, or if the process server runs out of RAM, then
264    the fault is unable to be handled and the faulting process is blocked indefinitely.
265
266    @param m The recieved IPC fault message from the kernel.
267    @param f The VM fault message info struct.
268*/
269static void
270handle_vm_fault(struct procserv_msg *m, struct procserv_vmfault_msg *f)
271{
272    assert(f && f->pcb);
273    dvprintf("# Process server recieved PID %d VM fault\n", f->pcb->pid);
274    dvprintf("# %s %s fault at 0x%x, Instruction Pointer 0x%x, Fault Status Register 0x%x\n",
275            f->instruction ? "Instruction" : "Data", f->read ? "read" : "write",
276            f->faultAddr, f->pc, f->fsr);
277
278    /* Thread should never be fault blocked (or else how did this VM fault even happen?). */
279    if (f->pcb->faultReply.capPtr != 0) {
280        ROS_ERROR("(how did this VM fault even happen? Check book-keeping.\n");
281        output_segmentation_fault("Process should already be fault-blocked.", f);
282        return;
283    }
284
285    /* Check faulting vaddr in segment windows. */
286    struct w_associated_window *aw = w_associate_find(&f->pcb->vspace.windows, f->faultAddr);
287    if (!aw) {
288        output_segmentation_fault("invalid memory window segment", f);
289        return;
290    }
291
292    /* Retrieve the associated window. */
293    struct w_window *window = w_get_window(&procServ.windowList, aw->winID);
294    if (!window) {
295        output_segmentation_fault("invalid memory window - procserv book-keeping error.", f);
296        assert(!"Process server could not find window - book-keeping error.");
297        return;
298    }
299
300    /* Check window permissions. */
301    if (f->read && !(window->permissions | W_PERMISSION_READ)) {
302        output_segmentation_fault("no read access permission to window.", f);
303        return;
304    }
305    if (!f->read && !(window->permissions | W_PERMISSION_WRITE)) {
306        output_segmentation_fault("no write access permission to window.", f);
307        return;
308    }
309
310    /* Check that there isn't a page entry already mapped. */
311    cspacepath_t pageEntry = vs_get_frame(&f->pcb->vspace, f->faultAddr);
312    if (pageEntry.capPtr != 0) {
313        output_segmentation_fault("entry already occupied; book-keeping error.", f);
314        return;
315    }
316
317    /* Handle the dispatch request depending on window mode. */
318    int error = EINVALID;
319    switch (window->mode) {
320        case W_MODE_EMPTY:
321            output_segmentation_fault("fault in empty window.", f);
322            break;
323        case W_MODE_ANONYMOUS:
324            error = handle_vm_fault_dspace(m, f, aw, window);
325            break;
326        case W_MODE_PAGER:
327            error = handle_vm_fault_pager(m, f, aw, window);
328            break;
329        default:
330            assert(!"Invalid window mode. Process server bug.");
331            break;
332    }
333
334    /* Reply to the faulting process to unblock it. */
335    if (error == ESUCCESS) {
336        seL4_Reply(_dispatcherEmptyReply);
337    }
338}
339
340/* ------------------------------------ Dispatcher functions ------------------------------------ */
341
342int
343check_dispatch_fault(struct procserv_msg *m, void **userptr) {
344    if (seL4_MessageInfo_get_label(m->message) != seL4_Fault_VMFault ||
345            !dispatcher_badge_PID(m->badge)) {
346        /* Not a VM fault, pass onto next dispatcher. */
347        return DISPATCH_PASS;
348    }
349    (void) userptr;
350    return DISPATCH_SUCCESS;
351}
352
353int
354dispatch_vm_fault(struct procserv_msg *m, void **userptr) {
355    if (check_dispatch_fault(m,  userptr) != DISPATCH_SUCCESS) {
356        return DISPATCH_PASS;
357    }
358    (void) userptr;
359
360    /* Find the faulting client's PCB. */
361    struct proc_pcb *pcb = pid_get_pcb_from_badge(&procServ.PIDList, m->badge);
362    if (!pcb) {
363        ROS_WARNING("Unknown client.");
364        return DISPATCH_ERROR;
365    }
366    assert(pcb->magic == REFOS_PCB_MAGIC);
367    assert(pcb->pid == m->badge - PID_BADGE_BASE);
368
369    /* Fill out the VM fault message info structure. */
370    struct procserv_vmfault_msg vmfault;
371    vmfault.pcb = pcb;
372    vmfault.pc = seL4_GetMR(seL4_VMFault_IP);
373    vmfault.faultAddr = seL4_GetMR(seL4_VMFault_Addr);
374    vmfault.instruction = seL4_GetMR(seL4_VMFault_PrefetchFault);
375    vmfault.fsr = seL4_GetMR(seL4_VMFault_FSR);
376    vmfault.read = sel4utils_is_read_fault();
377
378    /* Handle the VM fault. */
379    handle_vm_fault(m, &vmfault);
380    return DISPATCH_SUCCESS;
381}
382