aboutsummaryrefslogblamecommitdiff
path: root/sys/arm64/arm64/trap.c
blob: 4e5f9e32b80417533e5cb34da7af89c16f89ecdb (plain) (tree)
































                                                                             
                    

                      











                        
                        




                          
                              
 



                           











                               
                                 

                             

                                                       
                                      


                                             
                                                     
 

                                                  


                                                                           
                                 












                                            
                                            

  
                    
                                                                 




                                 

                            



                             
                                         


                       
                                




                                
                        







                                                                   







                                                             
                                                                    









                                    
                                                       
 
 
                                                      

                                 



                                                                               


           


                                                                     



                                                     
                                                              
         





                                                                       
                                                                    
                            

                           


                        
                              


                     
 






                                                                           






                              
                         
                        
                  
                                            
              

                              
                                                             
                                                
                                         
                        
                                                    


                                                 

         
          




                                                                               
           

                                                                    

                       









                                                                       


                                         
                                        

                      

                                                                  

                      
 
                                
                                                                              
                                    
                            
                                                                     






                                                                 





                                                             
          
                                               




                                                                             
                                               
                         
      







                                                                     




                                        
                                                         





                                                                     
                                                      

 
    
                                                        
 
                                 
                           
                          
                 

                                                                
                            

                                           




                                                                              
                      

                                                                           
 











                                     


                           
          
                                                                   








                                                             
                             
                             
                                               

                                                    
                                                   
                                                                     


                                                     
                                                             


                                                                          
                 

                      






                                                          
          

                                                                   


                                                  
                                   
                      


                              

                                                                   



                                                  



                                         
                
                                       
                                                                  


                                                                             

                              


    
                                                       
 
                                 
                           
                          
                 





                                                                          
                            
                                           

                               













                                                                           


                                               
                      

                      
 
                      

                                                                                
 








                                                   

                        
                                       


                               
                             



                                                                     



                                                             


                                                                            
                 
                      
                          


                                                                             
                      



                                                                              
                           


                                                                               
                            
                      


                                                                                



                                                                                


                                                          

                                                                



                                                        
                


                                                                               
         
 
                                                                  
                                                               
                
                                                                  
                                                                 

 


                                                                        
    
                                  
 
                          
 






                                             

 












                                             
/*-
 * Copyright (c) 2014 Andrew Turner
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 */

#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/ktr.h>
#include <sys/lock.h>
#include <sys/mutex.h>
#include <sys/proc.h>
#include <sys/ptrace.h>
#include <sys/syscall.h>
#include <sys/sysent.h>
#ifdef KDB
#include <sys/kdb.h>
#endif

#include <vm/vm.h>
#include <vm/pmap.h>
#include <vm/vm_kern.h>
#include <vm/vm_map.h>
#include <vm/vm_param.h>
#include <vm/vm_extern.h>

#include <machine/frame.h>
#include <machine/pcb.h>
#include <machine/pcpu.h>
#include <machine/undefined.h>

#ifdef KDTRACE_HOOKS
#include <sys/dtrace_bsd.h>
#endif

#ifdef VFP
#include <machine/vfp.h>
#endif

#ifdef KDB
#include <machine/db_machdep.h>
#endif

#ifdef DDB
#include <ddb/db_output.h>
#endif

extern register_t fsu_intr_fault;

/* Called from exception.S */
void do_el1h_sync(struct thread *, struct trapframe *);
void do_el0_sync(struct thread *, struct trapframe *);
void do_el0_error(struct trapframe *);
void do_serror(struct trapframe *);
void unhandled_exception(struct trapframe *);

static void print_registers(struct trapframe *frame);

int (*dtrace_invop_jump_addr)(struct trapframe *);

typedef void (abort_handler)(struct thread *, struct trapframe *, uint64_t,
    uint64_t, int);

static abort_handler align_abort;
static abort_handler data_abort;

static abort_handler *abort_handlers[] = {
	[ISS_DATA_DFSC_TF_L0] = data_abort,
	[ISS_DATA_DFSC_TF_L1] = data_abort,
	[ISS_DATA_DFSC_TF_L2] = data_abort,
	[ISS_DATA_DFSC_TF_L3] = data_abort,
	[ISS_DATA_DFSC_AFF_L1] = data_abort,
	[ISS_DATA_DFSC_AFF_L2] = data_abort,
	[ISS_DATA_DFSC_AFF_L3] = data_abort,
	[ISS_DATA_DFSC_PF_L1] = data_abort,
	[ISS_DATA_DFSC_PF_L2] = data_abort,
	[ISS_DATA_DFSC_PF_L3] = data_abort,
	[ISS_DATA_DFSC_ALIGN] = align_abort,
};

static __inline void
call_trapsignal(struct thread *td, int sig, int code, void *addr)
{
	ksiginfo_t ksi;

	ksiginfo_init_trap(&ksi);
	ksi.ksi_signo = sig;
	ksi.ksi_code = code;
	ksi.ksi_addr = addr;
	trapsignal(td, &ksi);
}

int
cpu_fetch_syscall_args(struct thread *td)
{
	struct proc *p;
	register_t *ap;
	struct syscall_args *sa;
	int nap;

	nap = 8;
	p = td->td_proc;
	ap = td->td_frame->tf_x;
	sa = &td->td_sa;

	sa->code = td->td_frame->tf_x[8];

	if (sa->code == SYS_syscall || sa->code == SYS___syscall) {
		sa->code = *ap++;
		nap--;
	}

	if (sa->code >= p->p_sysent->sv_size)
		sa->callp = &p->p_sysent->sv_table[0];
	else
		sa->callp = &p->p_sysent->sv_table[sa->code];

	sa->narg = sa->callp->sy_narg;
	memcpy(sa->args, ap, nap * sizeof(register_t));
	if (sa->narg > nap)
		panic("ARM64TODO: Could we have more than 8 args?");

	td->td_retval[0] = 0;
	td->td_retval[1] = 0;

	return (0);
}

#include "../../kern/subr_syscall.c"

static void
svc_handler(struct thread *td, struct trapframe *frame)
{

	if ((frame->tf_esr & ESR_ELx_ISS_MASK) == 0) {
		syscallenter(td);
		syscallret(td);
	} else {
		call_trapsignal(td, SIGILL, ILL_ILLOPN, (void *)frame->tf_elr);
		userret(td, frame);
	}
}

static void
align_abort(struct thread *td, struct trapframe *frame, uint64_t esr,
    uint64_t far, int lower)
{
	if (!lower) {
		print_registers(frame);
		printf(" far: %16lx\n", far);
		printf(" esr:         %.8lx\n", esr);
		panic("Misaligned access from kernel space!");
	}

	call_trapsignal(td, SIGBUS, BUS_ADRALN, (void *)frame->tf_elr);
	userret(td, frame);
}

static void
data_abort(struct thread *td, struct trapframe *frame, uint64_t esr,
    uint64_t far, int lower)
{
	struct vm_map *map;
	struct proc *p;
	struct pcb *pcb;
	vm_prot_t ftype;
	int error, sig, ucode;
#ifdef KDB
	bool handled;
#endif

	/*
	 * According to the ARMv8-A rev. A.g, B2.10.5 "Load-Exclusive
	 * and Store-Exclusive instruction usage restrictions", state
	 * of the exclusive monitors after data abort exception is unknown.
	 */
	clrex();

#ifdef KDB
	if (kdb_active) {
		kdb_reenter();
		return;
	}
#endif

	pcb = td->td_pcb;
	p = td->td_proc;
	if (lower)
		map = &p->p_vmspace->vm_map;
	else {
		intr_enable();

		/* The top bit tells us which range to use */
		if (far >= VM_MAXUSER_ADDRESS) {
			map = kernel_map;
		} else {
			map = &p->p_vmspace->vm_map;
			if (map == NULL)
				map = kernel_map;
		}
	}

	/*
	 * Try to handle translation, access flag, and permission faults.
	 * Translation faults may occur as a result of the required
	 * break-before-make sequence used when promoting or demoting
	 * superpages.  Such faults must not occur while holding the pmap lock,
	 * or pmap_fault() will recurse on that lock.
	 */
	if ((lower || map == kernel_map || pcb->pcb_onfault != 0) &&
	    pmap_fault(map->pmap, esr, far) == KERN_SUCCESS)
		return;

	KASSERT(td->td_md.md_spinlock_count == 0,
	    ("data abort with spinlock held"));
	if (td->td_critnest != 0 || WITNESS_CHECK(WARN_SLEEPOK |
	    WARN_GIANTOK, NULL, "Kernel page fault") != 0) {
		print_registers(frame);
		printf(" far: %16lx\n", far);
		printf(" esr:         %.8lx\n", esr);
		panic("data abort in critical section or under mutex");
	}

	switch (ESR_ELx_EXCEPTION(esr)) {
	case EXCP_INSN_ABORT:
	case EXCP_INSN_ABORT_L:
		ftype = VM_PROT_EXECUTE;
		break;
	default:
		ftype = (esr & ISS_DATA_WnR) == 0 ? VM_PROT_READ :
		    VM_PROT_READ | VM_PROT_WRITE;
		break;
	}

	/* Fault in the page. */
	error = vm_fault_trap(map, far, ftype, VM_FAULT_NORMAL, &sig, &ucode);
	if (error != KERN_SUCCESS) {
		if (lower) {
			call_trapsignal(td, sig, ucode, (void *)far);
		} else {
			if (td->td_intr_nesting_level == 0 &&
			    pcb->pcb_onfault != 0) {
				frame->tf_x[0] = error;
				frame->tf_elr = pcb->pcb_onfault;
				return;
			}

			printf("Fatal data abort:\n");
			print_registers(frame);
			printf(" far: %16lx\n", far);
			printf(" esr:         %.8lx\n", esr);

#ifdef KDB
			if (debugger_on_trap) {
				kdb_why = KDB_WHY_TRAP;
				handled = kdb_trap(ESR_ELx_EXCEPTION(esr), 0,
				    frame);
				kdb_why = KDB_WHY_UNSET;
				if (handled)
					return;
			}
#endif
			panic("vm_fault failed: %lx", frame->tf_elr);
		}
	}

	if (lower)
		userret(td, frame);
}

static void
print_registers(struct trapframe *frame)
{
	u_int reg;

	for (reg = 0; reg < nitems(frame->tf_x); reg++) {
		printf(" %sx%d: %16lx\n", (reg < 10) ? " " : "", reg,
		    frame->tf_x[reg]);
	}
	printf("  sp: %16lx\n", frame->tf_sp);
	printf("  lr: %16lx\n", frame->tf_lr);
	printf(" elr: %16lx\n", frame->tf_elr);
	printf("spsr:         %8x\n", frame->tf_spsr);
}

void
do_el1h_sync(struct thread *td, struct trapframe *frame)
{
	struct trapframe *oframe;
	uint32_t exception;
	uint64_t esr, far;
	int dfsc;

	/* Read the esr register to get the exception details */
	esr = frame->tf_esr;
	exception = ESR_ELx_EXCEPTION(esr);

#ifdef KDTRACE_HOOKS
	if (dtrace_trap_func != NULL && (*dtrace_trap_func)(frame, exception))
		return;
#endif

	CTR4(KTR_TRAP,
	    "do_el1_sync: curthread: %p, esr %lx, elr: %lx, frame: %p", td,
	    esr, frame->tf_elr, frame);

	oframe = td->td_frame;

	switch (exception) {
	case EXCP_BRK:
	case EXCP_WATCHPT_EL1:
	case EXCP_SOFTSTP_EL1:
		break;
	default:
		td->td_frame = frame;
		break;
	}

	switch(exception) {
	case EXCP_FP_SIMD:
	case EXCP_TRAP_FP:
#ifdef VFP
		if ((td->td_pcb->pcb_fpflags & PCB_FP_KERN) != 0) {
			vfp_restore_state();
		} else
#endif
		{
			print_registers(frame);
			printf(" esr:         %.8lx\n", esr);
			panic("VFP exception in the kernel");
		}
		break;
	case EXCP_INSN_ABORT:
	case EXCP_DATA_ABORT:
		far = READ_SPECIALREG(far_el1);
		dfsc = esr & ISS_DATA_DFSC_MASK;
		if (dfsc < nitems(abort_handlers) &&
		    abort_handlers[dfsc] != NULL) {
			abort_handlers[dfsc](td, frame, esr, far, 0);
		} else {
			print_registers(frame);
			printf(" far: %16lx\n", far);
			printf(" esr:         %.8lx\n", esr);
			panic("Unhandled EL1 %s abort: %x",
			    exception == EXCP_INSN_ABORT ? "instruction" :
			    "data", dfsc);
		}
		break;
	case EXCP_BRK:
#ifdef KDTRACE_HOOKS
		if ((esr & ESR_ELx_ISS_MASK) == 0x40d && \
		    dtrace_invop_jump_addr != 0) {
			dtrace_invop_jump_addr(frame);
			break;
		}
#endif
#ifdef KDB
		kdb_trap(exception, 0,
		    (td->td_frame != NULL) ? td->td_frame : frame);
#else
		panic("No debugger in kernel.\n");
#endif
		frame->tf_elr += 4;
		break;
	case EXCP_WATCHPT_EL1:
	case EXCP_SOFTSTP_EL1:
#ifdef KDB
		kdb_trap(exception, 0,
		    (td->td_frame != NULL) ? td->td_frame : frame);
#else
		panic("No debugger in kernel.\n");
#endif
		break;
	case EXCP_UNKNOWN:
		if (undef_insn(1, frame))
			break;
		/* FALLTHROUGH */
	default:
		print_registers(frame);
		printf(" far: %16lx\n", READ_SPECIALREG(far_el1));
		panic("Unknown kernel exception %x esr_el1 %lx\n", exception,
		    esr);
	}

	td->td_frame = oframe;
}

void
do_el0_sync(struct thread *td, struct trapframe *frame)
{
	pcpu_bp_harden bp_harden;
	uint32_t exception;
	uint64_t esr, far;
	int dfsc;

	/* Check we have a sane environment when entering from userland */
	KASSERT((uintptr_t)get_pcpu() >= VM_MIN_KERNEL_ADDRESS,
	    ("Invalid pcpu address from userland: %p (tpidr %lx)",
	     get_pcpu(), READ_SPECIALREG(tpidr_el1)));

	esr = frame->tf_esr;
	exception = ESR_ELx_EXCEPTION(esr);
	switch (exception) {
	case EXCP_INSN_ABORT_L:
		far = READ_SPECIALREG(far_el1);

		/*
		 * Userspace may be trying to train the branch predictor to
		 * attack the kernel. If we are on a CPU affected by this
		 * call the handler to clear the branch predictor state.
		 */
		if (far > VM_MAXUSER_ADDRESS) {
			bp_harden = PCPU_GET(bp_harden);
			if (bp_harden != NULL)
				bp_harden();
		}
		break;
	case EXCP_UNKNOWN:
	case EXCP_DATA_ABORT_L:
	case EXCP_DATA_ABORT:
		far = READ_SPECIALREG(far_el1);
		break;
	}
	intr_enable();

	CTR4(KTR_TRAP,
	    "do_el0_sync: curthread: %p, esr %lx, elr: %lx, frame: %p", td, esr,
	    frame->tf_elr, frame);

	switch(exception) {
	case EXCP_FP_SIMD:
	case EXCP_TRAP_FP:
#ifdef VFP
		vfp_restore_state();
#else
		panic("VFP exception in userland");
#endif
		break;
	case EXCP_SVC32:
	case EXCP_SVC64:
		svc_handler(td, frame);
		break;
	case EXCP_INSN_ABORT_L:
	case EXCP_DATA_ABORT_L:
	case EXCP_DATA_ABORT:
		dfsc = esr & ISS_DATA_DFSC_MASK;
		if (dfsc < nitems(abort_handlers) &&
		    abort_handlers[dfsc] != NULL)
			abort_handlers[dfsc](td, frame, esr, far, 1);
		else {
			print_registers(frame);
			printf(" far: %16lx\n", far);
			printf(" esr:         %.8lx\n", esr);
			panic("Unhandled EL0 %s abort: %x",
			    exception == EXCP_INSN_ABORT_L ? "instruction" :
			    "data", dfsc);
		}
		break;
	case EXCP_UNKNOWN:
		if (!undef_insn(0, frame))
			call_trapsignal(td, SIGILL, ILL_ILLTRP, (void *)far);
		userret(td, frame);
		break;
	case EXCP_SP_ALIGN:
		call_trapsignal(td, SIGBUS, BUS_ADRALN, (void *)frame->tf_sp);
		userret(td, frame);
		break;
	case EXCP_PC_ALIGN:
		call_trapsignal(td, SIGBUS, BUS_ADRALN, (void *)frame->tf_elr);
		userret(td, frame);
		break;
	case EXCP_BRKPT_EL0:
	case EXCP_BRK:
		call_trapsignal(td, SIGTRAP, TRAP_BRKPT, (void *)frame->tf_elr);
		userret(td, frame);
		break;
	case EXCP_MSR:
		call_trapsignal(td, SIGILL, ILL_PRVOPC, (void *)frame->tf_elr); 
		userret(td, frame);
		break;
	case EXCP_SOFTSTP_EL0:
		td->td_frame->tf_spsr &= ~PSR_SS;
		td->td_pcb->pcb_flags &= ~PCB_SINGLE_STEP;
		WRITE_SPECIALREG(mdscr_el1,
		    READ_SPECIALREG(mdscr_el1) & ~DBG_MDSCR_SS);
		call_trapsignal(td, SIGTRAP, TRAP_TRACE,
		    (void *)frame->tf_elr);
		userret(td, frame);
		break;
	default:
		call_trapsignal(td, SIGBUS, BUS_OBJERR, (void *)frame->tf_elr);
		userret(td, frame);
		break;
	}

	KASSERT((td->td_pcb->pcb_fpflags & ~PCB_FP_USERMASK) == 0,
	    ("Kernel VFP flags set while entering userspace"));
	KASSERT(
	    td->td_pcb->pcb_fpusaved == &td->td_pcb->pcb_fpustate,
	    ("Kernel VFP state in use when entering userspace"));
}

/*
 * TODO: We will need to handle these later when we support ARMv8.2 RAS.
 */
void
do_serror(struct trapframe *frame)
{
	uint64_t esr, far;

	far = READ_SPECIALREG(far_el1);
	esr = frame->tf_esr;

	print_registers(frame);
	printf(" far: %16lx\n", far);
	printf(" esr:         %.8lx\n", esr);
	panic("Unhandled System Error");
}

void
unhandled_exception(struct trapframe *frame)
{
	uint64_t esr, far;

	far = READ_SPECIALREG(far_el1);
	esr = frame->tf_esr;

	print_registers(frame);
	printf(" far: %16lx\n", far);
	printf(" esr:         %.8lx\n", esr);
	panic("Unhandled exception");
}