/* * hal_exception.c - implementation of exception handler for TSAR-MIPS32. * * Author Alain Greiner (2016, 2017) * * 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 #include #include #include #include #include #include #include #include #include #include ////////////////////////////////////////////////////////////////////////////////////////// // Extern global variables ////////////////////////////////////////////////////////////////////////////////////////// extern chdev_directory_t chdev_dir; // allocated in the kernel_init.c file. ////////////////////////////////////////////////////////////////////////////////////////// // This enum defines the global exception types after analysis by the exception handler. ////////////////////////////////////////////////////////////////////////////////////////// typedef enum { EXCP_NON_FATAL, EXCP_USER_ERROR, EXCP_KERNEL_PANIC, } exception_handling_type_t; ////////////////////////////////////////////////////////////////////////////////////////// // This enum defines the mask values for an MMU exception code reported by the mips32. ////////////////////////////////////////////////////////////////////////////////////////// typedef enum { MMU_WRITE_PT1_UNMAPPED = 0x0001, MMU_WRITE_PT2_UNMAPPED = 0x0002, MMU_WRITE_PRIVILEGE_VIOLATION = 0x0004, MMU_WRITE_ACCESS_VIOLATION = 0x0008, MMU_WRITE_UNDEFINED_XTN = 0x0020, MMU_WRITE_PT1_ILLEGAL_ACCESS = 0x0040, MMU_WRITE_PT2_ILLEGAL_ACCESS = 0x0080, MMU_WRITE_DATA_ILLEGAL_ACCESS = 0x0100, MMU_READ_PT1_UNMAPPED = 0x1001, MMU_READ_PT2_UNMAPPED = 0x1002, MMU_READ_PRIVILEGE_VIOLATION = 0x1004, MMU_READ_EXEC_VIOLATION = 0x1010, MMU_READ_UNDEFINED_XTN = 0x1020, MMU_READ_PT1_ILLEGAL_ACCESS = 0x1040, MMU_READ_PT2_ILLEGAL_ACCESS = 0x1080, MMU_READ_DATA_ILLEGAL_ACCESS = 0x1100, } mmu_exception_subtype_t; ////////////////////////////////////////////////////////////////////////////////////////// // This enum defines the relevant values for XCODE field in mips32 CP0_CR register. ////////////////////////////////////////////////////////////////////////////////////////// typedef enum { XCODE_ADEL = 0x4, // Illegal address for data load XCODE_ADES = 0x5, // Illegal address for data store XCODE_IBE = 0x6, // Instruction MMU exception (can be NON-FATAL) XCODE_DBE = 0x7, // Data MMU exception (can be NON-FATAL) XCODE_RI = 0xA, // Reserved instruction exception XCODE_CPU = 0xB, // Coprocessor unusable exception (can be NON-FATAl) XCODE_OVR = 0xC, // Arithmetic Overflow exception } xcode_values_t; ///////////////////////////////////////////// char * hal_mmu_exception_str( uint32_t code ) { if ( code == MMU_WRITE_PT1_UNMAPPED ) return "WRITE_PT1_UNMAPPED"; else if( code == MMU_WRITE_PT2_UNMAPPED ) return "WRITE_PT2_UNMAPPED"; else if( code == MMU_WRITE_PRIVILEGE_VIOLATION ) return "WRITE_PRIVILEGE_VIOLATION"; else if( code == MMU_WRITE_ACCESS_VIOLATION ) return "WRITE_ACCESS_VIOLATION"; else if( code == MMU_WRITE_UNDEFINED_XTN ) return "WRITE_UNDEFINED_XTN"; else if( code == MMU_WRITE_PT1_ILLEGAL_ACCESS ) return "WRITE_PT1_ILLEGAL_ACCESS"; else if( code == MMU_WRITE_PT2_ILLEGAL_ACCESS ) return "WRITE_PT2_ILLEGAL_ACCESS"; else if( code == MMU_WRITE_DATA_ILLEGAL_ACCESS ) return "WRITE_DATA_ILLEGAL_ACCESS"; else if( code == MMU_READ_PT1_UNMAPPED ) return "READ_PT1_UNMAPPED"; else if( code == MMU_READ_PT2_UNMAPPED ) return "READ_PT2_UNMAPPED"; else if( code == MMU_READ_PRIVILEGE_VIOLATION ) return "READ_PRIVILEGE_VIOLATION"; else if( code == MMU_READ_EXEC_VIOLATION ) return "READ_EXEC_VIOLATION"; else if( code == MMU_READ_UNDEFINED_XTN ) return "READ_UNDEFINED_XTN"; else if( code == MMU_READ_PT1_ILLEGAL_ACCESS ) return "READ_PT1_ILLEGAL_ACCESS"; else if( code == MMU_READ_PT2_ILLEGAL_ACCESS ) return "READ_PT2_ILLEGAL_ACCESS"; else if( code == MMU_READ_DATA_ILLEGAL_ACCESS ) return "READ_DATA_ILLEGAL_ACCESS"; else return "undefined"; } ////////////////////////////////////////////////////////////////////////////////////////// // This function is called when a FPU Coprocessor Unavailable exception has been // detected for 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. ////////////////////////////////////////////////////////////////////////////////////////// // @ this : pointer on faulty thread descriptor. // @ return always EXCP_NON_FATAL ////////////////////////////////////////////////////////////////////////////////////////// error_t hal_fpu_exception( thread_t * this ) { core_t * core = this->core; // enable FPU (in core SR) hal_fpu_enable(); // save FPU register values in current owner thread if required if( core->fpu_owner != NULL ) { if( core->fpu_owner != this ) { // save the FPU registers to current owner thread context hal_fpu_context_save( XPTR( local_cxy , core->fpu_owner ) ); // restore FPU registers from requesting thread context hal_fpu_context_restore( this ); // attach the FPU to the requesting thread core->fpu_owner = this; } } else { // restore FPU registers from requesting thread context hal_fpu_context_restore( this ); // attach the FPU to the requesting thread core->fpu_owner = this; } return EXCP_NON_FATAL; } // end hal_fpu_exception() ////////////////////////////////////////////////////////////////////////////////////////// // This function is called when an MMU exception has been detected (IBE / DBE). // It get the relevant exception arguments from the MMU. // It signal a fatal error in case of illegal access. In case of page unmapped // 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. // @ excPC : // @ is_ins : IBE if true / DBE if false. // @ return EXCP_NON_FATAL / EXCP_USER_ERROR / EXCP_KERNEL_PANIC ////////////////////////////////////////////////////////////////////////////////////////// error_t hal_mmu_exception( thread_t * this, uint32_t excPC, bool_t is_ins ) { process_t * process; error_t error; uint32_t mmu_ins_excp_code; uint32_t mmu_ins_bad_vaddr; uint32_t mmu_dat_excp_code; uint32_t mmu_dat_bad_vaddr; uint32_t bad_vaddr; uint32_t excp_code; process = this->process; // 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, depending on IBE/DBE if( is_ins ) { excp_code = mmu_ins_excp_code; bad_vaddr = mmu_ins_bad_vaddr; } else { excp_code = mmu_dat_excp_code; bad_vaddr = mmu_dat_bad_vaddr; } #if DEBUG_HAL_EXCEPTIONS uint32_t cycle = (uint32_t)hal_get_cycles(); if( DEBUG_HAL_EXCEPTIONS < cycle ) printk("\n[DBG] %s : thread %x in process %x enter / is_ins %d / %s / vaddr %x / cycle %d\n", __FUNCTION__, this->trdid, process->pid, is_ins, hal_mmu_exception_str(excp_code), bad_vaddr, cycle); #endif // analyse exception code switch( excp_code ) { case MMU_WRITE_PT1_UNMAPPED: // non fatal case MMU_WRITE_PT2_UNMAPPED: case MMU_READ_PT1_UNMAPPED: case MMU_READ_PT2_UNMAPPED: { // try to map the unmapped PTE error = vmm_handle_page_fault( process, bad_vaddr >> CONFIG_PPM_PAGE_SHIFT, // vpn false ); // not a COW if( error ) { printk("\n[USER ERROR] in %s for thread %x in process %x\n" " cannot map vaddr = %x / is_ins %d / epc %x\n", __FUNCTION__, this->trdid, this->process->pid, bad_vaddr, is_ins, excPC ); return EXCP_USER_ERROR; } else // page fault successfull { #if DEBUG_HAL_EXCEPTIONS cycle = (uint32_t)hal_get_cycles(); if( DEBUG_HAL_EXCEPTIONS < cycle ) printk("\n[DBG] %s : thread %x in process %x exit / page-fault handled for vaddr = %x\n", __FUNCTION__, this->trdid, process->pid, bad_vaddr ); #endif return EXCP_NON_FATAL; } } case MMU_WRITE_PRIVILEGE_VIOLATION: // illegal access user error case MMU_READ_PRIVILEGE_VIOLATION: { printk("\n[USER ERROR] in %s for thread %x in process %x\n" " illegal user access to vaddr = %x / is_ins %d / epc %x\n", __FUNCTION__, this->trdid, this->process->pid, bad_vaddr, is_ins, excPC ); return EXCP_USER_ERROR; } case MMU_WRITE_ACCESS_VIOLATION: // user error, or Copy-on-Write { // access page table to get GPT_COW flag bool_t cow = hal_gpt_pte_is_cow( &(process->vmm.gpt), bad_vaddr >> CONFIG_PPM_PAGE_SHIFT ); if( cow ) // Copy-on-Write { // try to allocate and copy the page error = vmm_handle_page_fault( process, bad_vaddr >> CONFIG_PPM_PAGE_SHIFT, // vpn true ); // COW if( error ) { printk("\n[USER ERROR] in %s for thread %x in process %x\n" " cannot cow vaddr = %x / is_ins %d / epc %x\n", __FUNCTION__, this->trdid, this->process->pid, bad_vaddr, is_ins, excPC ); return EXCP_USER_ERROR; } else // Copy on write successfull { #if DEBUG_HAL_EXCEPTIONS cycle = (uint32_t)hal_get_cycles(); if( DEBUG_HAL_EXCEPTIONS < cycle ) printk("\n[DBG] %s : thread %x in process %x exit / copy-on-write handled for vaddr = %x\n", __FUNCTION__, this->trdid, process->pid, bad_vaddr ); #endif return EXCP_NON_FATAL; } } else // non writable user error { printk("\n[USER ERROR] in %s for thread %x in process %x\n" " non-writable vaddr = %x / is_ins %d / epc %x\n", __FUNCTION__, this->trdid, this->process->pid, bad_vaddr, is_ins, excPC ); return EXCP_USER_ERROR; } } case MMU_READ_EXEC_VIOLATION: // user error { printk("\n[USER_ERROR] in %s for thread %x in process %x\n" " non-executable vaddr = %x / is_ins %d / epc %x\n", __FUNCTION__, this->trdid, this->process->pid, bad_vaddr, is_ins, excPC ); return EXCP_USER_ERROR; } default: // this is a kernel error { printk("\n[KERNEL ERROR] in %s for thread %x in process %x\n" " epc %x / badvaddr %x / is_ins %d\n", __FUNCTION__, this->trdid, this->process->pid, excPC, bad_vaddr, is_ins ); return EXCP_KERNEL_PANIC; } } } // end hal_mmu_exception() ////////////////////////////////////////////////////////////////////////////////////////// // This static function prints on the kernel terminal the saved context (core registers) // and the thread state of a faulty thread. ////////////////////////////////////////////////////////////////////////////////////////// // @ this : pointer on faulty thread descriptor. // @ uzone : pointer on register array. // @ error : EXCP_USER_ERROR or EXCP_KERNEL_PANIC ////////////////////////////////////////////////////////////////////////////////////////// static void hal_exception_dump( thread_t * this, reg_t * uzone, error_t error ) { uint32_t save_sr; core_t * core = this->core; process_t * process = this->process; // get pointers on TXT0 chdev xptr_t txt0_xp = chdev_dir.txt_tx[0]; cxy_t txt0_cxy = GET_CXY( txt0_xp ); chdev_t * txt0_ptr = GET_PTR( txt0_xp ); // get extended pointer on remote TXT0 chdev lock xptr_t lock_xp = XPTR( txt0_cxy , &txt0_ptr->wait_lock ); // get TXT0 lock in busy waiting mode remote_spinlock_lock_busy( lock_xp , &save_sr ); if( error == EXCP_USER_ERROR ) { nolock_printk("\n=== USER ERROR / trdid %x / pid %x / core[%x,%d] / cycle %d ===\n", this->trdid, process->pid, local_cxy, core->lid , (uint32_t)hal_get_cycles() ); } else { nolock_printk("\n=== KERNEL ERROR / trdid %x / pid %x / core[%x,%d] / cycle %d ===\n", this->trdid, process->pid, local_cxy, core->lid , (uint32_t)hal_get_cycles() ); } nolock_printk("local locks = %d / remote locks = %d / blocked_vector = %X\n\n", this->local_locks, this->remote_locks, this->blocked ); nolock_printk("c0_cr %X c0_epc %X c0_sr %X c0_th %X\n", uzone[UZ_CR], uzone[UZ_EPC], uzone[UZ_SR], uzone[UZ_TH] ); nolock_printk("c2_mode %X c2_ptpr %X\n", uzone[UZ_MODE], uzone[UZ_PTPR] ); nolock_printk("at_01 %X v0_2 %X v1_3 %X a0_4 %X a1_5 %X\n", uzone[UZ_AT], uzone[UZ_V0], uzone[UZ_V1], uzone[UZ_A0], uzone[UZ_A1] ); nolock_printk("a2_6 %X a3_7 %X t0_8 %X t1_9 %X t2_10 %X\n", uzone[UZ_A2], uzone[UZ_A3], uzone[UZ_T0], uzone[UZ_T1], uzone[UZ_T2] ); nolock_printk("t3_11 %X t4_12 %X t5_13 %X t6_14 %X t7_15 %X\n", uzone[UZ_T3], uzone[UZ_T4], uzone[UZ_T5], uzone[UZ_T6], uzone[UZ_T7] ); nolock_printk("s0_16 %X s1_17 %X s2_18 %X s3_19 %X s4_20 %X\n", uzone[UZ_S0], uzone[UZ_S1], uzone[UZ_S2], uzone[UZ_S3], uzone[UZ_S4] ); nolock_printk("s5_21 %X s6_22 %X s7_23 %X s8_24 %X ra_25 %X\n", uzone[UZ_S5], uzone[UZ_S6], uzone[UZ_S7], uzone[UZ_T8], uzone[UZ_T9] ); nolock_printk("gp_28 %X sp_29 %X S8_30 %X ra_31 %X\n", uzone[UZ_GP], uzone[UZ_SP], uzone[UZ_S8], uzone[UZ_RA] ); // release the lock remote_spinlock_unlock_busy( lock_xp , save_sr ); } // end hal_exception_dump() /////////////////////// void hal_do_exception( void ) { uint32_t * uzone; thread_t * this; error_t error; uint32_t excCode; // 4 bits XCODE from CP0_CR uint32_t excPC; // fauty instruction address // get pointer on faulty thread uzone this = CURRENT_THREAD; uzone = (uint32_t *)CURRENT_THREAD->uzone_current; // get XCODE and EPC from UZONE excCode = (uzone[UZ_CR] >> 2) & 0xF; excPC = uzone[UZ_EPC]; #if DEBUG_HAL_EXCEPTIONS uint32_t cycle = (uint32_t)hal_get_cycles(); if( DEBUG_HAL_EXCEPTIONS < cycle ) printk("\n[DBG] %s : thread %x in process %x enter / core[%x,%d] / epc %x / xcode %x / cycle %d\n", __FUNCTION__, this->trdid, this->process->pid, local_cxy, this->core->lid, excPC, excCode, cycle ); #endif switch(excCode) { case XCODE_DBE: // Data Bus Error : can be non fatal if page fault { error = hal_mmu_exception( this , excPC , false ); // data MMU exception break; } case XCODE_IBE: // Instruction Bus Error : can be non fatal if page fault { error = hal_mmu_exception( this , excPC , true ); // ins MMU exception break; } case XCODE_CPU: // Coprocessor unavailable : can be non fatal if FPU { if( ((uzone[UZ_CR] >> 28) & 0x3) == 1 ) // FPU { error = hal_fpu_exception( this ); } else // undefined coprocessor { printk("\n[USER_ERROR] in %s for thread %x in process %x\n" " undefined coprocessor / epc %x\n", __FUNCTION__, this->trdid, this->process->pid, excPC ); error = EXCP_USER_ERROR; } break; } case XCODE_OVR: // Arithmetic Overflow : user fatal error { printk("\n[USER_ERROR] in %s for thread %x in process %x\n" " arithmetic overflow / epc %x\n", __FUNCTION__, this->trdid, this->process->pid, excPC ); error = EXCP_USER_ERROR; break; } case XCODE_RI: // Reserved Instruction : user fatal error { printk("\n[USER_ERROR] in %s for thread %x in process %x\n" " reserved instruction / epc %x\n", __FUNCTION__, this->trdid, this->process->pid, excPC ); error = EXCP_USER_ERROR; break; } case XCODE_ADEL: // user fatal error { printk("\n[USER_ERROR] in %s for thread %x in process %x\n" " illegal data load address / epc %x\n", __FUNCTION__, this->trdid, this->process->pid, excPC ); error = EXCP_USER_ERROR; break; } case XCODE_ADES: // user fatal error { printk("\n[USER_ERROR] in %s for thread %x in process %x\n" " illegal data store address / epc %x\n", __FUNCTION__, this->trdid, this->process->pid, excPC ); error = EXCP_USER_ERROR; break; } default: { error = EXCP_KERNEL_PANIC; } } // analyse error code if( error == EXCP_USER_ERROR ) // user error => kill user process { hal_exception_dump( this , uzone , error ); sys_exit( EXIT_FAILURE ); } else if( error == EXCP_KERNEL_PANIC ) // kernel error => kernel panic { hal_exception_dump( this , uzone , error ); assert( false , "Exception raised kernel panic see information below.\n" ); } #if DEBUG_HAL_EXCEPTIONS cycle = (uint32_t)hal_get_cycles(); if( DEBUG_HAL_EXCEPTIONS < cycle ) printk("\n[DBG] %s : thread %x in process %x exit / core[%x,%d] / epc %x / xcode %x / cycle %d\n", __FUNCTION__, this->trdid, this->process->pid, local_cxy, this->core->lid, excPC, excCode, cycle ); #endif } // end hal_do_exception()