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