/* * do_exception.c - architecture independant exception handler implementation. * * Author Alain Greiner (2016) * * Copyright (c) UPMC Sorbonne Universites * * This file is part of ALMOS-MKH. * * ALMOS-MKH is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2.0 of the License. * * ALMOS-MKH is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with ALMOS-MKH; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #include ////////////////////////////////////////////////////////////////////////////////////////// // This file containss the architecture independant exception handler. // // It is called by the architecture specific hal_do_exception() function. // The fatal errors are mostly handled by the architecture specific handler, // and this generic exception handler handles only two non fatal exceptions types: // - MMU page fault // - FPU unavailable // // As some MMU exceptions can be fatal, the returned error code can take three values: // - Non Fatal Exception // - Fatal User Error // - Kernel Panic Error ////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////// // This remote_spinlock is a kernel global variable defined in all clusters. // It must be used by the architecture-specific exception handlers to display // error messages on the kernel terminal. Only the spinlock in the I/O cluster is used. ///////////////////////////////////////////////////////////////////////////////////////// remote_spinlock_t exception_lock; ////////////////////////////////////////////////////////////////////////////////////////// // This static function is called by the generic do_exception() handler when an MMU // exception has been detected. // It get the relevant exception arguments from the MMU. // It signal a fatal error in case of illegal access. In case of page fault, // it checks that the faulty address belongs to a registered vseg. It update the local // vseg list from the reference cluster if required, and signal a fatal user error // in case of illegal virtual address. Finally, it updates the local page table from the // reference cluster. ////////////////////////////////////////////////////////////////////////////////////////// // @ this : pointer on faulty thread descriptor. // @ return EXCP_NON_FATAL / EXCP_USER_ERROR / EXCP_KERNEL_PANIC ////////////////////////////////////////////////////////////////////////////////////////// static error_t mmu_exception( thread_t * this ) { vseg_t * vseg; // vseg containing the bad_vaddr process_t * process; // local process descriptor vmm_t * vmm; // VMM for calling thread error_t error; // return value reg_t mmu_ins_excp_code; reg_t mmu_ins_bad_vaddr; reg_t mmu_dat_excp_code; reg_t mmu_dat_bad_vaddr; intptr_t bad_vaddr; uint32_t excp_code; process = this->process; vmm = &process->vmm; // get relevant values from MMU hal_get_mmu_excp( &mmu_ins_excp_code, &mmu_ins_bad_vaddr, &mmu_dat_excp_code, &mmu_dat_bad_vaddr ); // get exception code and faulty vaddr if( mmu_ins_excp_code ) { excp_code = mmu_ins_excp_code; bad_vaddr = mmu_ins_bad_vaddr; } else if( mmu_dat_excp_code ) { excp_code = mmu_dat_excp_code; bad_vaddr = mmu_dat_bad_vaddr; } else { return EXCP_NON_FATAL; } vmm_dmsg("\n[INFO] %s : enters for thread %x / process %x" " / bad_vaddr = %x / excep_code = %x\n", __FUNCTION__, this->trdid , process->pid , bad_vaddr , excp_code ); // a kernel thread should not rise an MMU exception if( this->type != THREAD_USER ) { printk("\n[PANIC] in %s : thread %x is a kernel thread / vaddr = %x\n", __FUNCTION__ , this->trdid , bad_vaddr ); return EXCP_KERNEL_PANIC; } // enable IRQs hal_enable_irq( NULL ); // vaddr must be contained in a registered vseg vseg = vmm_get_vseg( process , bad_vaddr ); if( vseg == NULL ) // vseg not found in local cluster { // get extended pointer on reference process xptr_t ref_xp = process->ref_xp; // get cluster and local pointer on reference process cxy_t ref_cxy = GET_CXY( ref_xp ); process_t * ref_ptr = (process_t *)GET_PTR( ref_xp ); if( local_cxy != ref_cxy ) // reference process is remote { // get extended pointer on reference vseg xptr_t vseg_xp; rpc_vmm_get_ref_vseg_client( ref_cxy , ref_ptr , bad_vaddr , &vseg_xp ); if( vseg == NULL ) // vseg not found => illegal user vaddr { printk("\n[ERROR] in %s for thread %x : illegal vaddr = %x\n", __FUNCTION__ , this->trdid , bad_vaddr ); hal_disable_irq( NULL ); return EXCP_USER_ERROR; } else // vseg found => make a local copy { // allocate a vseg in local cluster vseg = vseg_alloc(); if( vseg == NULL ) { printk("\n[PANIC] in %s : no memory for vseg / thread = %x\n", __FUNCTION__ , this->trdid ); hal_disable_irq( NULL ); return EXCP_KERNEL_PANIC; } // initialise local vseg from reference vseg_init_from_ref( vseg , ref_xp ); // register local vseg in local VMM error = vseg_attach( &process->vmm , vseg ); } } else // reference is local => illegal user vaddr { printk("\n[ERROR] in %s for thread %x : illegal vaddr = %x\n", __FUNCTION__ , this->trdid , bad_vaddr ); hal_disable_irq( NULL ); return EXCP_USER_ERROR; } } vmm_dmsg("\n[INFO] %s : found vseg for thread %x / vseg_min = %x / vseg_max = %x\n", __FUNCTION__ , this->trdid , vseg->min , vseg->max ); // analyse exception code if( excp_code & MMU_EXCP_PAGE_UNMAPPED ) { // try to map the unmapped PTE error = vmm_handle_page_fault( process, vseg, bad_vaddr >> CONFIG_PPM_PAGE_SHIFT ); // vpn if( error ) { printk("\n[PANIC] in %s for thread %x : cannot map legal vaddr = %x\n", __FUNCTION__ , this->trdid , bad_vaddr ); hal_disable_irq( NULL ); return EXCP_KERNEL_PANIC; } else { vmm_dmsg("\n[INFO] %s : page fault handled for vaddr = %x in thread %x\n", __FUNCTION__ , bad_vaddr , this->trdid ); // page fault successfully handled hal_disable_irq( NULL ); // TODO Pourquoi ce yield ? [AG] // sched_yield(); return EXCP_NON_FATAL; } } else if( excp_code & MMU_EXCP_USER_PRIVILEGE ) { printk("\n[ERROR] in %s for thread %x : user access to kernel vseg at vaddr = %x\n", __FUNCTION__ , this->trdid , bad_vaddr ); hal_disable_irq( NULL ); return EXCP_USER_ERROR; } else if( excp_code & MMU_EXCP_USER_EXEC ) { printk("\n[ERROR] in %s for thread %x : access to non-exec vseg at vaddr = %x\n", __FUNCTION__ , this->trdid , bad_vaddr ); hal_disable_irq( NULL ); return EXCP_USER_ERROR; } else if( excp_code & MMU_EXCP_USER_WRITE ) { printk("\n[ERROR] in %s for thread %x : write to non-writable vseg at vaddr = %x\n", __FUNCTION__ , this->trdid , bad_vaddr ); hal_disable_irq( NULL ); return EXCP_USER_ERROR; } else // this is a kernel error => panic { printk("\n[PANIC] in %s for thread %x : kernel exception = %x / vaddr = %x\n", __FUNCTION__ , this->trdid , excp_code , bad_vaddr ); hal_disable_irq( NULL ); return EXCP_KERNEL_PANIC; } } // end mmu_exception_handler() ////////////////////////////////////////////////////////////////////////////////////////// // This static function is called by the generic do_exception() handler when a // FPU Coprocessor Unavailable exception has been detected by the calling thread. // It enables the FPU, It saves the current FPU context in the current owner thread // descriptor if required, and restore the FPU context from the calling thread descriptor. ////////////////////////////////////////////////////////////////////////////////////////// // @ thread : pointer on faulty thread descriptor. // @ return always EXCP_NON_FATAL ////////////////////////////////////////////////////////////////////////////////////////// static error_t fpu_exception( thread_t * this ) { core_t * core = this->core; // enable FPU hal_fpu_enable(); // save FPU context in current owner thread if required if( core->fpu_owner != NULL ) { if( core->fpu_owner != this ) { hal_fpu_context_save ( core->fpu_owner->fpu_context ); } } // attach the FPU to the requesting thread hal_fpu_context_restore( this->fpu_context ); core->fpu_owner = this; return EXCP_NON_FATAL; } ////////////////////////////////////// error_t do_exception( thread_t * this, bool_t is_mmu ) { error_t error; // update user time thread_user_time_update( this ); // try to handle non fatal exception if( is_mmu ) error = mmu_exception( this ); else error = fpu_exception( this ); // handle pending signals for interrupted thread thread_signals_handle( this ); // update kernel time thread_kernel_time_update( this ); return error; }