/* * hal_do_exception.c - implementation of exception handler for TSAR-MIPS32 * * Authors Ghassan Almaless (2008,2009,2010,2011,2012) * 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 #include #include #include #include #include #include #include ////////////////////////////////////////////////////////////////////////////////////////// // This enum defines the relevant values for the XCODE field from CP0_CR register. ////////////////////////////////////////////////////////////////////////////////////////// typedef enum { XCODE_ADEL = 4, // Illegal address for data load XCODE_ADES = 5, // Illegal address for data store XCODE_IBE = 6, // Instruction MMU exception XCODE_DBE = 7, // Data MMU exception XCODE_RI = 10, // Reserved instruction exception XCODE_FPU = 11, // FPU coprocessor exception XCODE_OVR = 12 // Arithmetic Overflow exception } xcode_values_t; ////////////////////////////////////////////////////////////////////////////////////////// // This defines the masks used to analyse the TSAR MMU exception code ////////////////////////////////////////////////////////////////////////////////////////// #define TSAR_MMU_PAGE_UNMAPPED 0x0003 // page fault (PTE unmapped) #define TSAR_MMU_USER_PRIVILEGE 0x0004 // user access to a kernel segment #define TSAR_MMU_USER_WRITE 0x0008 // user access to non writable segment #define TSAR_MMU_USER_EXEC 0x0010 // user access to non executable segment #define TSAR_MMU_KERNEL_XTN 0x0020 // kernel illegal external access #define TSAR_MMU_KERNEL_PT1 0x0040 // kernel illegal PT1 access #define TSAR_MMU_KERNEL_PT2 0x0080 // kernel illegal PT2 access #define TSAR_MMU_KERNEL_DATA 0x0100 // kernel illegal data access ////////////////////////////////////////////////////////////////////////////////////////// // This defines the masks used to get the TSAR MMU PTE attributes ////////////////////////////////////////////////////////////////////////////////////////// #define TSAR_MMU_PTE_V 0x80000000 // Valid #define TSAR_MMU_PTE_T 0x40000000 // Small Page #define TSAR_MMU_PTE_C 0x08000000 // Cachable #define TSAR_MMU_PTE_W 0x04000000 // Writable #define TSAR_MMU_PTE_X 0x02000000 // eXecutable #define TSAR_MMU_PTE_U 0x01000000 // User accessible ////////////////////////////////////////////////////////////////////////////////////////// // This enum defines the various types of error code returned to the hal_do_exception() // function by the mmu_exception_handler() and fpu_exception_handler(). ////////////////////////////////////////////////////////////////////////////////////////// typedef enum { EXCP_SOLVED = 0, // No error => the unmapped PTE has been mapped EXCP_USER_ERROR = 1, // User error => user process will receive a SIGSEGV EXCP_KERNEL_PANIC = 2, // Kernel error => kernel panic } mmu_excp_t; ///////////////////////////////////////////////////////////////////////////////////////// // This remote_spinlock is a global variable defined in all clusters, // but only the spinlock implemented in the boot cluster is used. ///////////////////////////////////////////////////////////////////////////////////////// __attribute__((section(".kdata"))) remote_spinlock_t exception_lock CONFIG_CACHE_LINE_ALIGNED; ///////////////////////////////////////////////////////////////////////////////////////// // This function is called by the hal_do_exception() function when a "FPU unusable" // exception has been detected by the calling thread. // This function check in CP0_CR register that the unavailable CPU is actually CP1, // it saves the FPU context in the owner thread descriptor, and restore the FPU context // from the calling thread descriptor. ///////////////////////////////////////////////////////////////////////////////////////// static error_t fpu_exception_handler( reg_t * regs_tbl ) { thread_t * this = CURRENT_THREAD; // calling thread core_t * core = this->core; // associated core // check coprocessor index if( ((regs_tbl[CR] >> 28) & 0x3) != 1 ) { printk(WARNING, "%s for thread %x : bad coprocessor indexn", __FUNCTION__ , this->trdid ); return EXCP_KERNEL_PANIC; } hal_fpu_enable(); if( (core->fpu_owner != NULL) && (core->fpu_owner != this) ) { hal_fpu_context_save ( &core->fpu_owner->uzone ); } hal_fpu_context_restore( &this->uzone ); cpu->fpu_owner = this; return EXCP_SOLVED; } /////////////////////////////////////////////////////////////////////////////////// // This function is called by the hal_do_exception() function when a TSAR-MMU // exception has been detected. There is three possible actions : // 1) simple page fault => page table is updated and thread resume. // 2) user error => user process is killed. // 3) kernel error => system crash. /////////////////////////////////////////////////////////////////////////////////// // @ excp_code : generic exception code returned by TSAR-MMU // @ bad_vaddr : faulty virtual address // @ return EXCP_RESOLVED / MMU_EXCP_USER_ERROR / MMU_EXCP_KERNEL_PANIC /////////////////////////////////////////////////////////////////////////////////// static error_t mmu_exception_handler( uint32_t excp_code, uint32_t bad_vaddr ) { thread_t * this; // calling thread pointer vseg_t * vseg; // vseg containing the bad_vaddr process_t * process; // local process descriptor vmm_t * vmm; // VMM for calling thread vpn_t vpn; // VPN for bad_vaddr uint32_t flags; // vseg flags error_t error; // return value this = CURRENT_THREAD; process = this->process; vmm = &process->vmm; vpn = bad_vaddr>>CONFIG_PPM_PAGE_SHIFT; vmm_dmsg(2, "%s enters for thread %x in process %x / bad_vaddr = %x / excep_code = %x\n", __FUNCTION__, this->trdid , process->pid , bad_vaddr , excep_code ); // a kernel thread should not rise an MMU exception if( this->type != T_USER ) { printk(WARNING, "%s for thread %x : it's a kernel thread / vaddr = %x\n", __FUNCTION__ , this->trdid , bad_vaddr ); return EXCP_KERNEL_PANIC; } // enable IRQs hal_enable_irq( NULL ); // update user_time tm_usr_compute( this ); // vaddr must be contained in a registered vseg vseg = vmm_get_vseg( process , bad_vaddr ); if( vseg == NULL ) // vseg not found { if( cxy != cxy_ref ) // try to get vseg from reference VMM { rpc_vmm_get_ref_vseg_client( cxy_ref , process_ref , bad_vaddr , &vseg ); } if( vseg == NULL ) // illegal user vaddr => return user error { printk(WARNING, "%s for thread %x : no vseg for vaddr = %x\n", __FUNCTION__ , this->trdid , bad_vaddr ); hal_disable_irq( NULL ); return EXCP_USER_ERROR; } else // legal vaddr => get vseg flags { flags = vseg->flags; } } vmm_dmsg(2, "%s found vseg for thread %x / vseg_base = %x / vseg_flags = %x\n", __FUNCTION__ , this->trdid , vseg->begin , vseg->flags ); // analyse TSAR MMU exception code if( excp_code & TSAR_MMU_UNMAPPED ) { // try to map the unmapped PTE error = vmm_handle_page_fault( process , vseg , vpn ); if( error ) { printk(WARNING, "%s for thread %x : cannot allocate memory for new PTE\n", __FUNCTION__ , this->trdid , bad_vaddr ); hal_disable_irq( NULL ); return EXCP_KERNEL_PANIC; } else { vmm_dmsg(2, "%s page fault succesfully handled for vaddr = %x in thread %x\n", __FUNCTION__ , bad_vaddr , this->trdid ); // page fault successfully handled hal_disable_irq( NULL ); hal_yield(); // TODO Pourquoi ce yield ? // update kernel_time tm_sys_compute(this); return EXCP_SOLVED; } } else if( excp_code & TSAR_MMU_USER_PRIVILEGE ) { printk(WARNING,"%s for thread %x : user access to kernel vseg at vaddr = %x\n", __FUNCTION__ , thread->trdid , bad_vaddr ); return EXCP_USER_ERROR; } else if( excp_code & TSAR_MMU_USER_EXEC ) { printk(WARNING,"%s for thread %x : access to non-exec vseg at vaddr = %x\n" __FUNCTION__ , thread->trdid , bad_vaddr ); return EXCP_USER_ERROR; } else if( excp_code & TSAR_MMU_USER_WRITE ) { printk(WARNING,"%s for thread %x : write to non-writable vseg at vaddr = %x\n" __FUNCTION__ , thread->trdid , bad_vaddr ); return EXCP_USER_ERROR; } else if( excp_code & TSAR_MMU_KERNEL_XTN ) { printk(WARNING,"%s for thread %x : kernel illegal access to external address = %x\n" __FUNCTION__ , thread->trdid , bad_vaddr ); return EXCP_KERNEL_PANIC; } else if( excp_code & TSAR_MMU_KERNEL_PT1 ) { printk(WARNING,"%s for thread %x : kernel bus error accessing PT1 / vaddr = %x\n" __FUNCTION__ , thread->trdid , bad_vaddr ); return EXCP_KERNEL_PANIC; } else if( excp_code & TSAR_MMU_KERNEL_PT2 ) { printk(WARNING,"%s for thread %x : kernel bus error accessing PT2 / vaddr = %x\n" __FUNCTION__ , thread->trdid , bad_vaddr ); return EXCP_KERNEL_PANIC; } else if( excp_code & TSAR_MMU_KERNEL_DATA ) { printk(WARNING,"%s for thread %x : kernel bus error accessing DATA / vaddr = %x\n" __FUNCTION__ , thread->trdid , bad_vaddr ); return EXCP_KERNEL_PANIC; } else { printk(WARNING,"%s for thread %x : undefined MMUexception code ??? / vaddr = %x\n" __FUNCTION__ , thread->trdid , bad_vaddr ); return EXCP_KERNEL_PANIC; } } // end mmu_exception_handler() /////////////////////////////////////// void hal_do_exception( thread_t * this, gid_t gid, reg_t * regs_tbl ) { error_t error; uint32_t excCode; // XCODE from CP0_CR uint32_t mmu_iexcp_code; // MMU IEXCP_CODE from CP2 uint32_t mmu_ibad_vaddr; // MMU IBAD_VADDR from CP2 uint32_t mmu_dexcp_code; // MMU DEXCP_CODE from CP2 uint32_t mmu_dbad_vaddr; // MMU BDAD_VADDR from CP2 bool_t isInKernelMode; mmu_except_info_t * entry; hal_except_info_t * execErr; // get XCODE from CP0_CR register excCode = (regs_tbl[CR] >> 2) & 0x1F; // get relevant values from CP2 registers mmu_iexcp_code = mips_get_cp2(MMU_IETR, 0); mmu_ibad_vaddr = mips_get_cp2(MMU_IBVAR, 0); mmu_dexcp_code = mips_get_cp2(MMU_DETR, 0); mmu_dbad_vaddr = mips_get_cp2(MMU_DBVAR, 0); switch(excCode) { case XCODE_IBE: error = mmu_exception_handler( mmu_iexcp_code , mmu_ibad_vaddr ); break; case XCODE_DBE: error = mmu_exception_handler( mmu_dexcp_code , mmu_dbad_vaddr ); break; case XCODE_CPU: error = fpu_exception_handler( regs_tbl ); break; default: error = EXCP_KERNEL_PANIC; break; } // analyse error code if( error == EXCP_SOLVED ) // page fault successfully handled => just return { return; } else if( error == EXCP_USER_ERROR ) // user error => kill the user process and return { // TODO [AG] // uspace_start = (uint32_t) &__uspace_start; // uspace_end = (uint32_t) &__uspace_end; // // if((regs_tbl[EPC] >= uspace_start) && (regs_tbl[EPC] <= uspace_end)) // { // regs_tbl[EPC] = (reg_t) &hal_uspace_error; // regs_tbl[MMU_MD] = (reg_t) 0x3;//MMU_MODE OFF // return; // } } else // kernel error => kernel panic { // take the exception_lock located in boot_cluster cxy_t boot_cxy = LOCAL_CLUSTER->boot_cxy; remote_spinlock_lock( XPTR( boot_cxy , &exception_lock ) ); thread_t * this = CURRENT_THREAD; process_t * process = this->process; // dump registers values except_dmsg("====================================================================\n"); except_dmsg("Kernel Panic: thread %x in process %x on core %x at cycle %d\n", this->trdid , process->pid , gid , hal_time_stamp() ); except_dmsg("Processor State:\n"); except_dmsg("CR: %x\tEPC: %x\tSR: %x\tSP: %x\tUSR SP %x\n", regs_tbl[CR],regs_tbl[EPC],regs_tbl[SR],regs_tbl[SP],this->uzone.regs[SP]); except_dmsg("at_1 %x\tv0_2 %x\t\tv1_3 %x\ta0_4 %x\ta1_5 %x\n", regs_tbl[AT],regs_tbl[V0],regs_tbl[V1],regs_tbl[A0],regs_tbl[A1]); except_dmsg("a2_6 %x\t\ta3_7 %x\tt0_8 %x\tt1_9 %x\tt2_10 %x\n", regs_tbl[A2],regs_tbl[A3],regs_tbl[T0],regs_tbl[T1],regs_tbl[T2]); except_dmsg("t3_11 %x\tt4_12 %x\t\tt5_13 %x\tt6_14 %x\tt7_15 %x\n", regs_tbl[T3],regs_tbl[T4],regs_tbl[T5],regs_tbl[T6],regs_tbl[T7]); except_dmsg("t8_24 %x\t\tt9_25 %x\tgp_28 %x\tc0_hi %x\tc0_lo %x\n", regs_tbl[T8],regs_tbl[T9],regs_tbl[GP],regs_tbl[HI],regs_tbl[LO]); except_dmsg("s0_16 %x\ts1_17 %x\ts2_18 %x\ts3_19 %x\ts4_20 %x\n", regs_tbl[S0],regs_tbl[S1],regs_tbl[S2],regs_tbl[S3],regs_tbl[S4]); except_dmsg("s5_21 %x\ts6_22 %x\t\ts7_23 %x\ts8_30 %x\tra_31 %x\n\n", regs_tbl[S5],regs_tbl[S6],regs_tbl[S7],regs_tbl[S8],regs_tbl[RA]); except_dmsg("Thread State %x\n" "\tsys_stack_top = %x\n" "tusr_stack = %x\n" "\tutls = %x\n" "\tstate = %s\n" "\tlocks = %d\n", this->trdid, this->uzone.regs[KSP], this->uzone.regs[SP], this->uzone.regs[TLS_K1], thread_get_state_name( this->state ), this->locks_count); isInKernelMode = (regs_tbl[SR] & 0x10) ? false : true; except_dmsg("\nIs in kernel: %s\n", (isInKernelMode) ? "YES" : "NO"); if(isInKernelMode) { execErr = hal_except_get_entry(excCode); if(regs_tbl[EPC] >= __ktext_start && regs_tbl[EPC] <= __ktext_end) instContent = *((uint32_t*) regs_tbl[EPC]); else instContent = 0; except_dmsg("Pid %d, Cpu %d, Inst. %x, Exception : code %d, name %s, description %s, bad address %x\n", this->task->pid, gid, instContent, excCode, execErr->name, execErr->desc, hal_get_bad_vaddr() ); } except_dmsg("====================================================================\n"); // release exception lock remote_spinlock_unlock( XPTR( boot_cxy , &exception_lock ) ); sched_exit(this); // TODO ??? [AG] while(entry != NULL); }